├── README.md ├── hellotriangle ├── AAPLRenderer.h ├── AAPLRenderer.m ├── AAPLShaderTypes.h ├── AAPLShaders.metal └── LICENSE.txt ├── hellotriangleshaders.qrc ├── main.mm └── qt-and-metal.pro /README.md: -------------------------------------------------------------------------------- 1 | Qt and Metal 2 | ============ 3 | 4 | This example shows how to combine Metal and Qt by using 5 | a QWindow instance to control a MTKView instance. The QWindow 6 | can be shown as a top-level window or embedded as a child of 7 | another window. 8 | 9 | Shaders are compiled using custom qmake targets. The compiled 10 | shader library is embedded in the executable as a Qt resource. 11 | 12 | ```C++ 13 | 14 | MTKView *view = [[[MTKView alloc] init] autorelease]; 15 | (Metal view setup omitted) 16 | 17 | window = QWindow::fromWinId((WId)view); 18 | window->resize(640, 480); 19 | window->show(); 20 | 21 | ``` 22 | 23 | ![QWindow with MTKView](https://user-images.githubusercontent.com/296277/29745576-ed0f2e34-8abe-11e7-9088-b6ca163c1bff.png?s=263) 24 | -------------------------------------------------------------------------------- /hellotriangle/AAPLRenderer.h: -------------------------------------------------------------------------------- 1 | /* 2 | See LICENSE folder for this sample’s licensing information. 3 | 4 | Abstract: 5 | Header for renderer class which perfoms Metal setup and per frame rendering 6 | */ 7 | 8 | @import MetalKit; 9 | 10 | // Our platform independent render class 11 | @interface AAPLRenderer : NSObject 12 | 13 | - (nonnull instancetype)initWithMetalKitView:(nonnull MTKView *)mtkView library:(nonnull id)library; 14 | 15 | @end 16 | -------------------------------------------------------------------------------- /hellotriangle/AAPLRenderer.m: -------------------------------------------------------------------------------- 1 | /* 2 | See LICENSE folder for this sample’s licensing information. 3 | 4 | Abstract: 5 | Implementation of renderer class which perfoms Metal setup and per frame rendering 6 | */ 7 | @import simd; 8 | @import MetalKit; 9 | 10 | #import "AAPLRenderer.h" 11 | 12 | // Header shared between C code here, which executes Metal API commands, and .metal files, which 13 | // uses these types as inpute to the shaders 14 | #import "AAPLShaderTypes.h" 15 | 16 | // Main class performing the rendering 17 | @implementation AAPLRenderer 18 | { 19 | // The device (aka GPU) we're using to render 20 | id _device; 21 | 22 | // Our render pipeline composed of our vertex and fragment shaders in the .metal shader file 23 | id _pipelineState; 24 | 25 | // The command Queue from which we'll obtain command buffers 26 | id _commandQueue; 27 | 28 | // The current size of our view so we can use this in our render pipeline 29 | vector_uint2 _viewportSize; 30 | } 31 | 32 | /// Initialize with the MetalKit view from which we'll obtain our metal device 33 | - (nonnull instancetype)initWithMetalKitView:(nonnull MTKView *)mtkView library:(nonnull id)library 34 | { 35 | self = [super init]; 36 | if(self) 37 | { 38 | NSError *error = NULL; 39 | 40 | _device = mtkView.device; 41 | 42 | // Indicate we would like to use the RGBAPisle format. 43 | mtkView.colorPixelFormat = MTLPixelFormatBGRA8Unorm_sRGB; 44 | 45 | // Modification for Qt: use library loaded at runtime 46 | //id defaultLibrary = [_device newDefaultLibrary]; 47 | id defaultLibrary = library; 48 | 49 | // Load the vertex function from the library 50 | id vertexFunction = [defaultLibrary newFunctionWithName:@"vertexShader"]; 51 | 52 | // Load the fragment function from the library 53 | id fragmentFunction = [defaultLibrary newFunctionWithName:@"fragmentShader"]; 54 | 55 | // Set up a descriptor for creating a pipeline state object 56 | MTLRenderPipelineDescriptor *pipelineStateDescriptor = [[MTLRenderPipelineDescriptor alloc] init]; 57 | pipelineStateDescriptor.label = @"Simple Pipeline"; 58 | pipelineStateDescriptor.vertexFunction = vertexFunction; 59 | pipelineStateDescriptor.fragmentFunction = fragmentFunction; 60 | pipelineStateDescriptor.colorAttachments[0].pixelFormat = mtkView.colorPixelFormat; 61 | 62 | _pipelineState = [_device newRenderPipelineStateWithDescriptor:pipelineStateDescriptor 63 | error:&error]; 64 | if (!_pipelineState) 65 | { 66 | // Pipeline State creation could fail if we haven't properly set up our pipeline descriptor. 67 | // If the Metal API validation is enabled, we can find out more information about what 68 | // went wrong. (Metal API validation is enabled by default when a debug build is run 69 | // from Xcode) 70 | NSLog(@"Failed to created pipeline state, error %@", error); 71 | return nil; 72 | } 73 | 74 | // Create the command queue 75 | _commandQueue = [_device newCommandQueue]; 76 | } 77 | 78 | return self; 79 | } 80 | 81 | /// Called whenever view changes orientation or is resized 82 | - (void)mtkView:(nonnull MTKView *)view drawableSizeWillChange:(CGSize)size 83 | { 84 | // Save the size of the drawable as we'll pass these 85 | // values to our vertex shader when we draw 86 | _viewportSize.x = size.width; 87 | _viewportSize.y = size.height; 88 | } 89 | 90 | /// Called whenever the view needs to render a frame 91 | - (void)drawInMTKView:(nonnull MTKView *)view 92 | { 93 | static const AAPLVertex triangleVertices[] = 94 | { 95 | // 2D Positions, RGBA colors 96 | { { 250, -250 }, { 1, 0, 0, 1 } }, 97 | { { -250, -250 }, { 0, 1, 0, 1 } }, 98 | { { 0, 250 }, { 0, 0, 1, 1 } }, 99 | }; 100 | 101 | // Create a new command buffer for each renderpass to the current drawable 102 | id commandBuffer = [_commandQueue commandBuffer]; 103 | commandBuffer.label = @"MyCommand"; 104 | 105 | // Obtain a renderPassDescriptor generated from the view's drawable textures 106 | MTLRenderPassDescriptor *renderPassDescriptor = view.currentRenderPassDescriptor; 107 | 108 | if(renderPassDescriptor != nil) 109 | { 110 | // Create a render command encoder so we can render into something 111 | id renderEncoder = 112 | [commandBuffer renderCommandEncoderWithDescriptor:renderPassDescriptor]; 113 | renderEncoder.label = @"MyRenderEncoder"; 114 | 115 | // Set the region of the drawable to which we'll draw. 116 | [renderEncoder setViewport:(MTLViewport){0.0, 0.0, _viewportSize.x, _viewportSize.y, -1.0, 1.0 }]; 117 | 118 | [renderEncoder setRenderPipelineState:_pipelineState]; 119 | 120 | // We call -[MTLRenderCommandEncoder setVertexBytes:lenght:atIndex:] tp send data from our 121 | // Application ObjC code here to our Metal 'vertexShader' function 122 | // This call has 3 arguments 123 | // 1) A pointer to the memory we want to pass to our shader 124 | // 2) The memory size of the data we want passed down 125 | // 3) An integer index which corresponds to the index of the buffer attribute qualifier 126 | // of the argument in our 'vertexShader' function 127 | 128 | // Here we're sending a pointer to our 'triangleVertices' array (and indicating its size). 129 | // The AAPLVertexInputIndexVertices enum value corresponds to the 'vertexArray' argument 130 | // in our 'vertexShader' function because its buffer attribute qualifier also uses 131 | // AAPLVertexInputIndexVertices for its index 132 | [renderEncoder setVertexBytes:triangleVertices 133 | length:sizeof(triangleVertices) 134 | atIndex:AAPLVertexInputIndexVertices]; 135 | 136 | // Here we're sending a pointer to '_viewportSize' and also indicate its size so the whole 137 | // think is passed into the shader. The AAPLVertexInputIndexViewportSize enum value 138 | /// corresponds to the 'viewportSizePointer' argument in our 'vertexShader' function 139 | // because its buffer attribute qualifier also uses AAPLVertexInputIndexViewportSize 140 | // for its index 141 | [renderEncoder setVertexBytes:&_viewportSize 142 | length:sizeof(_viewportSize) 143 | atIndex:AAPLVertexInputIndexViewportSize]; 144 | 145 | // Draw the 3 vertices of our triangle 146 | [renderEncoder drawPrimitives:MTLPrimitiveTypeTriangle 147 | vertexStart:0 148 | vertexCount:3]; 149 | 150 | [renderEncoder endEncoding]; 151 | 152 | // Schedule a present once the framebuffer is complete using the current drawable 153 | [commandBuffer presentDrawable:view.currentDrawable]; 154 | } 155 | 156 | // Finalize rendering here & push the command buffer to the GPU 157 | [commandBuffer commit]; 158 | } 159 | 160 | @end 161 | -------------------------------------------------------------------------------- /hellotriangle/AAPLShaderTypes.h: -------------------------------------------------------------------------------- 1 | /* 2 | See LICENSE folder for this sample’s licensing information. 3 | 4 | Abstract: 5 | Header containing types and enum constants shared between Metal shaders and C/ObjC source 6 | */ 7 | #ifndef ShaderTypes_h 8 | #define ShaderTypes_h 9 | 10 | #include 11 | 12 | // Buffer index values shared between shader and C code to ensure Metal shader buffer inputs match 13 | // Metal API buffer set calls 14 | typedef enum AAPLVertexInputIndex 15 | { 16 | AAPLVertexInputIndexVertices = 0, 17 | AAPLVertexInputIndexViewportSize = 1, 18 | } AAPLVertexInputIndex; 19 | 20 | 21 | // This structure defines the layout of each vertex in the array of vertices set as an input to our 22 | // Metal vertex shader. Since this header is shared between our .metal shader and C code, 23 | // we can be sure that the layout of the vertex array in the Ccode matches the layour that 24 | // our vertex shader expects 25 | typedef struct 26 | { 27 | // Positions in pixel space (i.e. a value of 100 indicates 100 pixels from the origin/center) 28 | vector_float2 position; 29 | 30 | // Floating point RGBA colors 31 | vector_float4 color; 32 | } AAPLVertex; 33 | 34 | #endif /* ShaderTypes_h */ 35 | -------------------------------------------------------------------------------- /hellotriangle/AAPLShaders.metal: -------------------------------------------------------------------------------- 1 | /* 2 | See LICENSE folder for this sample’s licensing information. 3 | 4 | Abstract: 5 | Metal shaders used for this sample 6 | */ 7 | 8 | #include 9 | #include 10 | 11 | using namespace metal; 12 | 13 | // Include header shared between this Metal shader code and C code executing Metal API commands 14 | #import "AAPLShaderTypes.h" 15 | 16 | // Vertex shader outputs and fragmentShader inputs. 17 | typedef struct 18 | { 19 | // The [[position]] attribute qualifier of this member indicates this value is the clip space 20 | // position of the vertex when this structure is returned from the vertex function 21 | float4 clipSpacePosition [[position]]; 22 | 23 | // Since this member does not have a special attribute qualifier, the rasterizer will 24 | // interpolate its value with values of other vertices making up the triangle and 25 | // pass that interpolated value to the fragment shader for each fragment in that triangle 26 | float4 color; 27 | 28 | } RasterizerData; 29 | 30 | // Vertex Function 31 | vertex RasterizerData 32 | vertexShader(uint vertexID [[vertex_id]], 33 | constant AAPLVertex *vertices [[buffer(AAPLVertexInputIndexVertices)]], 34 | constant vector_uint2 *viewportSizePointer [[buffer(AAPLVertexInputIndexViewportSize)]]) 35 | { 36 | RasterizerData out; 37 | 38 | // Initialize our output clip space position 39 | out.clipSpacePosition = vector_float4(0.0, 0.0, 0.0, 1.0); 40 | 41 | // Index into our array of positions to get the current vertex 42 | // Our positons are specified in pixel dimensions (i.e. a value of 100 is 100 pixels from 43 | // the origin) 44 | float2 pixelSpacePosition = vertices[vertexID].position.xy; 45 | 46 | // Dereference viewportSizePointer and cast to float so we can do floating-point division 47 | vector_float2 viewportSize = vector_float2(*viewportSizePointer); 48 | 49 | // The output position of every vertex shader is in clip space (also known as normalized device 50 | // coordinate space, or NDC). A value of (-1.0, -1.0) in clip-space represents the 51 | // lower-left corner of the viewport whereas (1.0, 1.0) represents the upper-right corner of 52 | // the viewport. 53 | 54 | // Calculate and write x and y values to our clip-space position. In order to convert from 55 | // positions in pixel space to positions in clip-space, we divide the pixel coordinates by 56 | // half the size of the viewport. 57 | out.clipSpacePosition.xy = pixelSpacePosition / (viewportSize / 2.0); 58 | 59 | // Pass our input color straight to our output color. This value will be interpolated 60 | // with the other color values in the vertices that make up the triangle to produce 61 | // the color value for each fragment in our fragmentShader 62 | out.color = vertices[vertexID].color; 63 | 64 | return out; 65 | } 66 | 67 | // Fragment function 68 | fragment float4 fragmentShader(RasterizerData in [[stage_in]]) 69 | { 70 | // We return the color we just set which will be written to our color attachment. 71 | return in.color; 72 | } 73 | 74 | -------------------------------------------------------------------------------- /hellotriangle/LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright © 2017 Apple Inc. 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 8 | 9 | -------------------------------------------------------------------------------- /hellotriangleshaders.qrc: -------------------------------------------------------------------------------- 1 | 2 | 3 | hellotriangleshaders.metallib 4 | 5 | -------------------------------------------------------------------------------- /main.mm: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | 5 | int main(int argc, char **argv) 6 | { 7 | // This example demonstrates how to integrate Metal rendering 8 | // with Qt by controlling a Metal view using a QWindow. The Metal 9 | // part of the example is copied from the hellotriangle example. 10 | 11 | QGuiApplication app(argc, argv); 12 | QWindow *window = nullptr; 13 | AAPLRenderer *renderer = nil; 14 | 15 | // Create UI after event loop has started. 16 | QTimer::singleShot(0, [&window, &renderer]() { 17 | 18 | // Create Metal view and default device 19 | MTKView *view = [[[MTKView alloc] init] autorelease]; 20 | view.device = MTLCreateSystemDefaultDevice(); 21 | if (!view.device) 22 | qFatal("Metal is not supported"); 23 | 24 | // Load shaders from Qt resources 25 | QFile shadersFile(":/hellotriangleshaders.metallib"); 26 | shadersFile.open(QIODevice::ReadOnly); 27 | QByteArray shaders = shadersFile.readAll(); 28 | dispatch_data_t data = dispatch_data_create(shaders.constData(), shaders.size(), 29 | dispatch_get_main_queue(), DISPATCH_DATA_DESTRUCTOR_DEFAULT); 30 | NSError *error = nil; 31 | id library = [view.device newLibraryWithData:data error:&error]; 32 | dispatch_release(data); 33 | if (error) 34 | qWarning() << "Shader Error" << error; 35 | 36 | // Create Renderer 37 | renderer = [[AAPLRenderer alloc] initWithMetalKitView:view library:library]; 38 | [renderer mtkView:view drawableSizeWillChange:view.drawableSize]; 39 | view.delegate = renderer; 40 | 41 | // Create and show a Qt Window which controls the metal view 42 | window = QWindow::fromWinId((WId)view); 43 | window->setTitle("QWindow with MTKView"); 44 | window->resize(640, 480); 45 | window->show(); 46 | }); 47 | 48 | // Start the event loop 49 | int code = app.exec(); 50 | 51 | // Clean up on exit. The QWindow will release the MTKView on destruction. 52 | delete window; 53 | [renderer release]; 54 | 55 | return code; 56 | } -------------------------------------------------------------------------------- /qt-and-metal.pro: -------------------------------------------------------------------------------- 1 | TEMPLATE = app 2 | 3 | # hellotriangle example code 4 | QMAKE_OBJECTIVE_CFLAGS += -fmodules -fcxx-modules 5 | INCLUDEDIRS += hellotriangle 6 | OBJECTIVE_SOURCES += hellotriangle/AAPLRenderer.m 7 | 8 | # hellotriangle example shaders 9 | hellotriangleshaders_air.target = hellotriangleshaders.air 10 | hellotriangleshaders_air.commands = xcrun -sdk macosx metal hellotriangle/AAPLShaders.metal -o hellotriangleshaders.air 11 | hellotriangleshaders_lib.target = hellotriangleshaders.metallib 12 | hellotriangleshaders_lib.commands = xcrun -sdk macosx metallib hellotriangleshaders.air -o hellotriangleshaders.metallib 13 | hellotriangleshaders_lib.depends = hellotriangleshaders_air 14 | QMAKE_EXTRA_TARGETS += hellotriangleshaders_air hellotriangleshaders_lib 15 | rcc.depends += $$hellotriangleshaders_lib.target 16 | RESOURCES += hellotriangleshaders.qrc 17 | 18 | # Qt main 19 | SOURCES += main.mm 20 | --------------------------------------------------------------------------------