├── hellotriangleshaders.qrc
├── hellotriangle
├── AAPLRenderer.h
├── LICENSE.txt
├── AAPLShaderTypes.h
├── AAPLShaders.metal
└── AAPLRenderer.m
├── README.md
├── qt-and-metal.pro
└── main.mm
/hellotriangleshaders.qrc:
--------------------------------------------------------------------------------
1 |
2 |
3 | hellotriangleshaders.metallib
4 |
5 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 | 
24 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 | }
--------------------------------------------------------------------------------
/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/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 |
--------------------------------------------------------------------------------