// // Copyright Contributors to the MaterialX Project // SPDX-License-Identifier: Apache-2.0 // #include "MetalState.h" #import #include MATERIALX_NAMESPACE_BEGIN std::unique_ptr MetalState::singleton = nullptr; MetalState::MetalState() { } void MetalState::initialize(id mtlDevice, id mtlCmdQueue) { device = mtlDevice; cmdQueue = mtlCmdQueue; #ifdef MAC_OS_VERSION_11_0 if (@available(macOS 11.0, ios 14.0, *)) { supportsTiledPipeline = [device supportsFamily:MTLGPUFamilyApple4]; } #else supportsTiledPipeline = false; #endif MTLDepthStencilDescriptor* depthStencilDesc = [MTLDepthStencilDescriptor new]; depthStencilDesc.depthWriteEnabled = true; depthStencilDesc.depthCompareFunction = MTLCompareFunctionLess; opaqueDepthStencilState = [device newDepthStencilStateWithDescriptor:depthStencilDesc]; depthStencilDesc.depthWriteEnabled = false; depthStencilDesc.depthCompareFunction = MTLCompareFunctionLess; transparentDepthStencilState = [device newDepthStencilStateWithDescriptor:depthStencilDesc]; depthStencilDesc.depthWriteEnabled = true; depthStencilDesc.depthCompareFunction = MTLCompareFunctionAlways; envMapDepthStencilState = [device newDepthStencilStateWithDescriptor:depthStencilDesc]; initLinearToSRGBKernel(); } void MetalState::initLinearToSRGBKernel() { NSError* error = nil; MTLCompileOptions* options = [MTLCompileOptions new]; #ifdef MAC_OS_VERSION_11_0 if (@available(macOS 11.0, ios 14.0, *)) options.languageVersion = MTLLanguageVersion2_3; else #endif options.languageVersion = MTLLanguageVersion2_0; options.fastMathEnabled = true; #ifdef MAC_OS_VERSION_11_0 bool useTiledPipeline = supportsTiledPipeline; if (useTiledPipeline) { if (@available(macOS 11.0, ios 14.0, *)) { NSString* linearToSRGB_kernel = @"#include \n" "#include \n" " \n" "using namespace metal; \n" " \n" "struct RenderTarget { \n" " half4 colorTarget [[color(0)]]; \n" "}; \n" " \n" " \n" " \n" "half4 linearToSRGB(half4 color_linear) \n" "{ \n" " half4 color_srgb; \n" " for(int i = 0; i < 3; ++i) \n" " color_srgb[i] = (color_linear[i] < 0.0031308) ? \n" " (12.92 * color_linear[i]) : \n" " (1.055 * pow(color_linear[i], 1.0h / 2.2h) - 0.055); \n" " color_srgb[3] = color_linear[3]; \n" " return color_srgb; \n" "} \n" " \n" "kernel void LinearToSRGB_kernel( \n" " imageblock imageBlock, \n" " ushort2 tid [[ thread_position_in_threadgroup ]]) \n" "{ \n" " RenderTarget linearValue = imageBlock.read(tid); \n" " RenderTarget srgbValue; \n" " srgbValue.colorTarget = linearToSRGB(linearValue.colorTarget); \n" " imageBlock.write(srgbValue, tid); \n" "} \n"; id library = [device newLibraryWithSource:linearToSRGB_kernel options:options error:&error]; id function = [library newFunctionWithName:@"LinearToSRGB_kernel"]; MTLTileRenderPipelineDescriptor* renderPipelineDescriptor = [MTLTileRenderPipelineDescriptor new]; [renderPipelineDescriptor setRasterSampleCount:1]; [[renderPipelineDescriptor colorAttachments][0] setPixelFormat:MTLPixelFormatRGBA16Float]; [renderPipelineDescriptor setTileFunction:function]; linearToSRGB_pso = [device newRenderPipelineStateWithTileDescriptor:renderPipelineDescriptor options:0 reflection:nil error:&error]; } else { useTiledPipeline = false; } } if (!useTiledPipeline) #endif { NSString* linearToSRGB_kernel = @"#include \n" "#include \n" " \n" "using namespace metal; \n" " \n" "struct VSOutput \n" "{ \n" " float4 position [[position]]; \n" "}; \n" " \n" "vertex VSOutput VertexMain(uint vertexId [[ vertex_id ]]) \n" "{ \n" " VSOutput vsOut; \n" " \n" " switch(vertexId) \n" " { \n" " case 0: vsOut.position = float4(-1, -1, 0.5, 1); break; \n" " case 1: vsOut.position = float4(-1, 3, 0.5, 1); break; \n" " case 2: vsOut.position = float4( 3, -1, 0.5, 1); break; \n" " }; \n" " \n" " return vsOut; \n" "} \n" " \n" "half4 linearToSRGB(half4 color_linear) \n" "{ \n" " half4 color_srgb; \n" " for(int i = 0; i < 3; ++i) \n" " color_srgb[i] = (color_linear[i] < 0.0031308) ? \n" " (12.92 * color_linear[i]) : \n" " (1.055 * pow(color_linear[i], 1.0h / 2.2h) - 0.055);\n" " color_srgb[3] = color_linear[3]; \n" " return color_srgb; \n" "} \n" " \n" "fragment half4 FragmentMain( \n" " texture2d inputTex [[ texture(0) ]], \n" " float4 fragCoord [[ position ]] \n" ") \n" "{ \n" " constexpr sampler ss( \n" " coord::pixel, \n" " address::clamp_to_border, \n" " filter::linear); \n" " return linearToSRGB(inputTex.sample(ss, fragCoord.xy)); \n" "} \n"; id library = [device newLibraryWithSource:linearToSRGB_kernel options:options error:&error]; id vertexfunction = [library newFunctionWithName:@"VertexMain"]; id Fragmentfunction = [library newFunctionWithName:@"FragmentMain"]; MTLRenderPipelineDescriptor* renderPipelineDesc = [MTLRenderPipelineDescriptor new]; [renderPipelineDesc setVertexFunction:vertexfunction]; [renderPipelineDesc setFragmentFunction:Fragmentfunction]; [[renderPipelineDesc colorAttachments][0] setPixelFormat:MTLPixelFormatRGBA16Float]; [renderPipelineDesc setDepthAttachmentPixelFormat:MTLPixelFormatDepth32Float]; linearToSRGB_pso = [device newRenderPipelineStateWithDescriptor:renderPipelineDesc error:&error]; } } void MetalState::triggerProgrammaticCapture() { MTLCaptureManager* captureManager = [MTLCaptureManager sharedCaptureManager]; MTLCaptureDescriptor* captureDescriptor = [MTLCaptureDescriptor new]; [captureDescriptor setCaptureObject:device]; NSError* error = nil; if (![captureManager startCaptureWithDescriptor:captureDescriptor error:&error]) { NSLog(@"Failed to start capture, error %@", error); } } void MetalState::stopProgrammaticCapture() { MTLCaptureManager* captureManager = [MTLCaptureManager sharedCaptureManager]; [captureManager stopCapture]; } void MetalState::beginCommandBuffer() { cmdBuffer = [cmdQueue commandBuffer]; inFlightCommandBuffers++; } void MetalState::beginEncoder(MTLRenderPassDescriptor* renderpassDesc) { renderCmdEncoder = [cmdBuffer renderCommandEncoderWithDescriptor:renderpassDesc]; } void MetalState::endEncoder() { [renderCmdEncoder endEncoding]; } void MetalState::endCommandBuffer() { endEncoder(); [cmdBuffer addCompletedHandler:^(id _Nonnull) { inFlightCommandBuffers--; inFlightCV.notify_one(); }]; [cmdBuffer commit]; [cmdBuffer waitUntilCompleted]; } void MetalState::waitForCompletion() { std::unique_lock lock(inFlightMutex); while (inFlightCommandBuffers != 0) { inFlightCV.wait(lock, [this] { return inFlightCommandBuffers.load() == 0; }); } } MaterialX::MetalFramebufferPtr MetalState::currentFramebuffer() { return framebufferStack.top(); } MATERIALX_NAMESPACE_END