2
0
Эх сурвалжийг харах

Renderer: implemented Metal rendering

osy 6 жил өмнө
parent
commit
6cd269432d

+ 3 - 2
CocoaSpice/CSDisplayMetal.h

@@ -15,13 +15,14 @@
 //
 //
 
 
 #import <Foundation/Foundation.h>
 #import <Foundation/Foundation.h>
-#import <CoreGraphics/CoreGraphics.h>
+#import "UTMRendererDelegate.h"
+@import CoreGraphics;
 
 
 typedef struct _SpiceSession SpiceSession;
 typedef struct _SpiceSession SpiceSession;
 
 
 NS_ASSUME_NONNULL_BEGIN
 NS_ASSUME_NONNULL_BEGIN
 
 
-@interface CSDisplayMetal : NSObject
+@interface CSDisplayMetal : NSObject <UTMRendererDelegate>
 
 
 @property (nonatomic, assign) BOOL ready;
 @property (nonatomic, assign) BOOL ready;
 @property (nonatomic, readonly, assign) SpiceSession *session;
 @property (nonatomic, readonly, assign) SpiceSession *session;

+ 117 - 23
CocoaSpice/CSDisplayMetal.m

@@ -15,6 +15,7 @@
 //
 //
 
 
 #import "CSDisplayMetal.h"
 #import "CSDisplayMetal.h"
+#import "UTMShaderTypes.h"
 #import <glib.h>
 #import <glib.h>
 #import <spice-client.h>
 #import <spice-client.h>
 #import <spice/protocol.h>
 #import <spice/protocol.h>
@@ -28,15 +29,20 @@
 @implementation CSDisplayMetal {
 @implementation CSDisplayMetal {
     SpiceDisplayChannel     *_display;
     SpiceDisplayChannel     *_display;
     
     
-    gint                    _mark;
+    BOOL                    _sigsconnected;
+    
+    /* Modifying the following REQUIRES lock held */
+    //gint                    _mark;
     gint                    _canvasFormat;
     gint                    _canvasFormat;
     gint                    _canvasStride;
     gint                    _canvasStride;
-    void                    *_canvasData;
+    const void              *_canvasData;
     CGRect                  _canvasArea;
     CGRect                  _canvasArea;
     CGRect                  _visibleArea;
     CGRect                  _visibleArea;
-    GWeakRef _overlay_weak_ref;
-    
-    BOOL                    _sigsconnected;
+    GWeakRef                _overlay_weak_ref;
+    id<MTLDevice>           _device;
+    id<MTLTexture>          _texture;
+    id<MTLBuffer>           _vertices;
+    NSUInteger              _numVertices;
 }
 }
 
 
 static void cs_primary_create(SpiceChannel *channel, gint format,
 static void cs_primary_create(SpiceChannel *channel, gint format,
@@ -44,10 +50,13 @@ static void cs_primary_create(SpiceChannel *channel, gint format,
                            gint shmid, gpointer imgdata, gpointer data) {
                            gint shmid, gpointer imgdata, gpointer data) {
     CSDisplayMetal *self = (__bridge CSDisplayMetal *)data;
     CSDisplayMetal *self = (__bridge CSDisplayMetal *)data;
     
     
-    self->_canvasArea = CGRectMake(0, 0, width, height);
-    self->_canvasFormat = format;
-    self->_canvasStride = stride;
-    self->_canvasData = imgdata;
+    g_assert(format == SPICE_SURFACE_FMT_32_xRGB || format == SPICE_SURFACE_FMT_16_555);
+    @synchronized (self) {
+        self->_canvasArea = CGRectMake(0, 0, width, height);
+        self->_canvasFormat = format;
+        self->_canvasStride = stride;
+        self->_canvasData = imgdata;
+    }
     
     
     cs_update_monitor_area(channel, data);
     cs_update_monitor_area(channel, data);
 }
 }
@@ -55,21 +64,31 @@ static void cs_primary_create(SpiceChannel *channel, gint format,
 static void cs_primary_destroy(SpiceDisplayChannel *channel, gpointer data) {
 static void cs_primary_destroy(SpiceDisplayChannel *channel, gpointer data) {
     CSDisplayMetal *self = (__bridge CSDisplayMetal *)data;
     CSDisplayMetal *self = (__bridge CSDisplayMetal *)data;
     self.ready = NO;
     self.ready = NO;
-    self->_canvasArea = CGRectZero;
-    self->_canvasFormat = 0;
-    self->_canvasStride = 0;
-    self->_canvasData = NULL;
+    @synchronized (self) {
+        self->_canvasArea = CGRectZero;
+        self->_canvasFormat = 0;
+        self->_canvasStride = 0;
+        self->_canvasData = NULL;
+        self->_texture = nil;
+    }
 }
 }
 
 
 static void cs_invalidate(SpiceChannel *channel,
 static void cs_invalidate(SpiceChannel *channel,
                        gint x, gint y, gint w, gint h, gpointer data) {
                        gint x, gint y, gint w, gint h, gpointer data) {
     CSDisplayMetal *self = (__bridge CSDisplayMetal *)data;
     CSDisplayMetal *self = (__bridge CSDisplayMetal *)data;
-#warning Unimplemented
+    @synchronized (self) {
+        CGRect rect = CGRectIntersection(CGRectMake(x, y, w, h), self->_visibleArea);
+        if (!CGRectIsEmpty(rect)) {
+            [self drawRegion:rect];
+        }
+    }
 }
 }
 
 
 static void cs_mark(SpiceChannel *channel, gint mark, gpointer data) {
 static void cs_mark(SpiceChannel *channel, gint mark, gpointer data) {
-    CSDisplayMetal *self = (__bridge CSDisplayMetal *)data;
-    self->_mark = mark;
+    //CSDisplayMetal *self = (__bridge CSDisplayMetal *)data;
+    //@synchronized (self) {
+    //    self->_mark = mark; // currently this does nothing for us
+    //}
 }
 }
 
 
 static gboolean cs_set_overlay(SpiceChannel *channel, void* pipeline_ptr, gpointer data) {
 static gboolean cs_set_overlay(SpiceChannel *channel, void* pipeline_ptr, gpointer data) {
@@ -194,6 +213,22 @@ static void cs_channel_destroy(SpiceSession *s, SpiceChannel *channel, gpointer
     return;
     return;
 }
 }
 
 
+- (void)setDevice:(id<MTLDevice>)device {
+    @synchronized (self) {
+        _device = device;
+    }
+    [self rebuildTexture];
+    [self rebuildVertices];
+}
+
+- (id<MTLDevice>)device {
+    return _device;
+}
+
+@synthesize texture;
+@synthesize numVertices;
+@synthesize vertices;
+
 - (id)initWithSession:(nonnull SpiceSession *)session channelID:(NSInteger)channelID monitorID:(NSInteger)monitorID {
 - (id)initWithSession:(nonnull SpiceSession *)session channelID:(NSInteger)channelID monitorID:(NSInteger)monitorID {
     self = [self init];
     self = [self init];
     if (self) {
     if (self) {
@@ -236,13 +271,72 @@ static void cs_channel_destroy(SpiceSession *s, SpiceChannel *channel, gpointer
 }
 }
 
 
 - (void)updateVisibleAreaWithRect:(CGRect)rect {
 - (void)updateVisibleAreaWithRect:(CGRect)rect {
-    CGRect visible = CGRectIntersection(_canvasArea, rect);
-    if (CGRectIsNull(visible)) {
-        DISPLAY_DEBUG(self, "The monitor area is not intersecting primary surface");
-        self.ready = NO;
-        _visibleArea = CGRectZero;
-    } else {
-        _visibleArea = visible;
+    @synchronized (self) {
+        CGRect visible = CGRectIntersection(_canvasArea, rect);
+        if (CGRectIsNull(visible)) {
+            DISPLAY_DEBUG(self, "The monitor area is not intersecting primary surface");
+            self.ready = NO;
+            _visibleArea = CGRectZero;
+        } else {
+            _visibleArea = visible;
+        }
+    }
+    [self rebuildTexture];
+    [self rebuildVertices];
+}
+
+- (void)rebuildTexture {
+    @synchronized (self) {
+        if (CGRectIsEmpty(_canvasArea) || !_device) {
+            return;
+        }
+        MTLTextureDescriptor *textureDescriptor = [[MTLTextureDescriptor alloc] init];
+        // don't worry that that components are reversed, we fix it in shaders
+        textureDescriptor.pixelFormat = (_canvasFormat == SPICE_SURFACE_FMT_32_xRGB) ? MTLPixelFormatBGRA8Unorm : MTLPixelFormatBGR5A1Unorm;
+        textureDescriptor.width = _visibleArea.size.width;
+        textureDescriptor.height = _visibleArea.size.height;
+        _texture = [_device newTextureWithDescriptor:textureDescriptor];
+        [self drawRegion:_visibleArea];
+    }
+}
+
+- (void)rebuildVertices {
+    @synchronized (self) {
+        // We flip the y-coordinates because pixman renders flipped
+        UTMVertex quadVertices[] =
+        {
+            // Pixel positions, Texture coordinates
+            { {  _visibleArea.size.width/2,   _visibleArea.size.height/2 },  { 1.f, 0.f } },
+            { { -_visibleArea.size.width/2,   _visibleArea.size.height/2 },  { 0.f, 0.f } },
+            { { -_visibleArea.size.width/2,  -_visibleArea.size.height/2 },  { 0.f, 1.f } },
+            
+            { {  _visibleArea.size.width/2,   _visibleArea.size.height/2 },  { 1.f, 0.f } },
+            { { -_visibleArea.size.width/2,  -_visibleArea.size.height/2 },  { 0.f, 1.f } },
+            { {  _visibleArea.size.width/2,  -_visibleArea.size.height/2 },  { 1.f, 1.f } },
+        };
+        
+        // Create our vertex buffer, and initialize it with our quadVertices array
+        _vertices = [_device newBufferWithBytes:quadVertices
+                                         length:sizeof(quadVertices)
+                                        options:MTLResourceStorageModeShared];
+    
+        // Calculate the number of vertices by dividing the byte length by the size of each vertex
+        _numVertices = sizeof(quadVertices) / sizeof(UTMVertex);
+    }
+}
+
+- (void)drawRegion:(CGRect)rect {
+    @synchronized (self) {
+        NSInteger pixelSize = (_canvasFormat == SPICE_SURFACE_FMT_32_xRGB) ? 4 : 2;
+        // copy texture
+        MTLRegion region = {
+            { rect.origin.x-_visibleArea.origin.x, rect.origin.y-_visibleArea.origin.y, 0 }, // MTLOrigin
+            { rect.size.width, rect.size.height, 1} // MTLSize
+        };
+        [_texture replaceRegion:region
+                    mipmapLevel:0
+                      withBytes:(const char *)_canvasData + (NSUInteger)(rect.origin.y*_canvasStride + rect.origin.x*pixelSize)
+                    bytesPerRow:_canvasStride];
     }
     }
 }
 }
 
 

+ 8 - 0
Renderer/LICENSE.txt

@@ -0,0 +1,8 @@
+Copyright © 2018 Apple Inc. 
+
+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:
+
+The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+
+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.
+

+ 18 - 0
Renderer/UTMRenderer.h

@@ -0,0 +1,18 @@
+/*
+See LICENSE folder for this sample’s licensing information.
+
+Abstract:
+Header for renderer class which performs Metal setup and per frame rendering
+*/
+
+#import "UTMRendererDelegate.h"
+@import MetalKit;
+
+// Our platform independent renderer class
+@interface UTMRenderer : NSObject<MTKViewDelegate>
+
+@property (nonatomic, weak, nullable) id<UTMRendererDelegate> delegate;
+
+- (nonnull instancetype)initWithMetalKitView:(nonnull MTKView *)mtkView;
+
+@end

+ 140 - 0
Renderer/UTMRenderer.m

@@ -0,0 +1,140 @@
+/*
+See LICENSE folder for this sample’s licensing information.
+
+Abstract:
+Implementation of renderer class which performs Metal setup and per frame rendering
+*/
+
+@import simd;
+@import MetalKit;
+
+#import "UTMRenderer.h"
+
+// Header shared between C code here, which executes Metal API commands, and .metal files, which
+//   uses these types as inputs to the shaders
+#import "UTMShaderTypes.h"
+
+// Main class performing the rendering
+@implementation UTMRenderer
+{
+    // The device (aka GPU) we're using to render
+    id<MTLDevice> _device;
+
+    // Our render pipeline composed of our vertex and fragment shaders in the .metal shader file
+    id<MTLRenderPipelineState> _pipelineState;
+
+    // The command Queue from which we'll obtain command buffers
+    id<MTLCommandQueue> _commandQueue;
+
+    // The current size of our view so we can use this in our render pipeline
+    vector_uint2 _viewportSize;
+}
+
+/// Initialize with the MetalKit view from which we'll obtain our Metal device
+- (nonnull instancetype)initWithMetalKitView:(nonnull MTKView *)mtkView
+{
+    self = [super init];
+    if(self)
+    {
+        _device = mtkView.device;
+
+        /// Create our render pipeline
+
+        // Load all the shader files with a .metal file extension in the project
+        id<MTLLibrary> defaultLibrary = [_device newDefaultLibrary];
+
+        // Load the vertex function from the library
+        id<MTLFunction> vertexFunction = [defaultLibrary newFunctionWithName:@"vertexShader"];
+
+        // Load the fragment function from the library
+        id<MTLFunction> fragmentFunction = [defaultLibrary newFunctionWithName:@"samplingShader"];
+
+        // Set up a descriptor for creating a pipeline state object
+        MTLRenderPipelineDescriptor *pipelineStateDescriptor = [[MTLRenderPipelineDescriptor alloc] init];
+        pipelineStateDescriptor.label = @"Texturing Pipeline";
+        pipelineStateDescriptor.vertexFunction = vertexFunction;
+        pipelineStateDescriptor.fragmentFunction = fragmentFunction;
+        pipelineStateDescriptor.colorAttachments[0].pixelFormat = mtkView.colorPixelFormat;
+
+        NSError *error = NULL;
+        _pipelineState = [_device newRenderPipelineStateWithDescriptor:pipelineStateDescriptor
+                                                                 error:&error];
+        if (!_pipelineState)
+        {
+            // Pipeline State creation could fail if we haven't properly set up our pipeline descriptor.
+            //  If the Metal API validation is enabled, we can find out more information about what
+            //  went wrong.  (Metal API validation is enabled by default when a debug build is run
+            //  from Xcode)
+            NSLog(@"Failed to created pipeline state, error %@", error);
+        }
+
+        // Create the command queue
+        _commandQueue = [_device newCommandQueue];
+    }
+
+    return self;
+}
+
+/// Called whenever view changes orientation or is resized
+- (void)mtkView:(nonnull MTKView *)view drawableSizeWillChange:(CGSize)size
+{
+    // Save the size of the drawable as we'll pass these
+    //   values to our vertex shader when we draw
+    _viewportSize.x = size.width;
+    _viewportSize.y = size.height;
+}
+
+/// Called whenever the view needs to render a frame
+- (void)drawInMTKView:(nonnull MTKView *)view
+{
+
+    // Create a new command buffer for each render pass to the current drawable
+    id<MTLCommandBuffer> commandBuffer = [_commandQueue commandBuffer];
+    commandBuffer.label = @"MyCommand";
+
+    // Obtain a renderPassDescriptor generated from the view's drawable textures
+    MTLRenderPassDescriptor *renderPassDescriptor = view.currentRenderPassDescriptor;
+
+    if(renderPassDescriptor != nil)
+    {
+        // Create a render command encoder so we can render into something
+        id<MTLRenderCommandEncoder> renderEncoder =
+        [commandBuffer renderCommandEncoderWithDescriptor:renderPassDescriptor];
+        renderEncoder.label = @"MyRenderEncoder";
+
+        // Set the region of the drawable to which we'll draw.
+        [renderEncoder setViewport:(MTLViewport){0.0, 0.0, _viewportSize.x, _viewportSize.y, -1.0, 1.0 }];
+
+        [renderEncoder setRenderPipelineState:_pipelineState];
+
+        [renderEncoder setVertexBuffer:self.delegate.vertices
+                                offset:0
+                              atIndex:UTMVertexInputIndexVertices];
+
+        [renderEncoder setVertexBytes:&_viewportSize
+                               length:sizeof(_viewportSize)
+                              atIndex:UTMVertexInputIndexViewportSize];
+
+        // Set the texture object.  The UTMTextureIndexBaseColor enum value corresponds
+        ///  to the 'colorMap' argument in our 'samplingShader' function because its
+        //   texture attribute qualifier also uses UTMTextureIndexBaseColor for its index
+        [renderEncoder setFragmentTexture:self.delegate.texture
+                                  atIndex:UTMTextureIndexBaseColor];
+
+        // Draw the vertices of our triangles
+        [renderEncoder drawPrimitives:MTLPrimitiveTypeTriangle
+                          vertexStart:0
+                          vertexCount:self.delegate.numVertices];
+
+        [renderEncoder endEncoding];
+
+        // Schedule a present once the framebuffer is complete using the current drawable
+        [commandBuffer presentDrawable:view.currentDrawable];
+    }
+
+
+    // Finalize rendering here & push the command buffer to the GPU
+    [commandBuffer commit];
+}
+
+@end

+ 31 - 0
Renderer/UTMRendererDelegate.h

@@ -0,0 +1,31 @@
+//
+// Copyright © 2019 Halts. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#import <Foundation/Foundation.h>
+@import MetalKit;
+
+NS_ASSUME_NONNULL_BEGIN
+
+@protocol UTMRendererDelegate <NSObject>
+
+@property (nonatomic, nullable, weak) id<MTLDevice> device;
+@property (nonatomic, nullable, readonly) id<MTLTexture> texture;
+@property (nonatomic, readonly) NSUInteger numVertices;
+@property (nonatomic, nullable, readonly) id<MTLBuffer> vertices;
+
+@end
+
+NS_ASSUME_NONNULL_END

+ 41 - 0
Renderer/UTMShaderTypes.h

@@ -0,0 +1,41 @@
+/*
+See LICENSE folder for this sample’s licensing information.
+
+Abstract:
+Header containing types and enum constants shared between Metal shaders and C/ObjC source
+*/
+
+#ifndef UTMShaderTypes_h
+#define UTMShaderTypes_h
+
+#include <simd/simd.h>
+
+// Buffer index values shared between shader and C code to ensure Metal shader buffer inputs match
+//   Metal API buffer set calls
+typedef enum UTMVertexInputIndex
+{
+    UTMVertexInputIndexVertices     = 0,
+    UTMVertexInputIndexViewportSize = 1,
+} UTMVertexInputIndex;
+
+// Texture index values shared between shader and C code to ensure Metal shader buffer inputs match
+//   Metal API texture set calls
+typedef enum UTMTextureIndex
+{
+    UTMTextureIndexBaseColor = 0,
+} UTMTextureIndex;
+
+//  This structure defines the layout of each vertex in the array of vertices set as an input to our
+//    Metal vertex shader.  Since this header is shared between our .metal shader and C code,
+//    we can be sure that the layout of the vertex array in the code matches the layout that
+//    our vertex shader expects
+typedef struct
+{
+    // Positions in pixel space (i.e. a value of 100 indicates 100 pixels from the origin/center)
+    vector_float2 position;
+
+    // 2D texture coordinate
+    vector_float2 textureCoordinate;
+} UTMVertex;
+
+#endif /* UTMShaderTypes_h */

+ 88 - 0
Renderer/UTMShaders.metal

@@ -0,0 +1,88 @@
+/*
+See LICENSE folder for this sample’s licensing information.
+
+Abstract:
+Metal shaders used for this sample
+*/
+
+#include <metal_stdlib>
+#include <simd/simd.h>
+
+using namespace metal;
+
+// Include header shared between this Metal shader code and C code executing Metal API commands
+#import "UTMShaderTypes.h"
+
+// Vertex shader outputs and per-fragment inputs. Includes clip-space position and vertex outputs
+//  interpolated by rasterizer and fed to each fragment generated by clip-space primitives.
+typedef struct
+{
+    // The [[position]] attribute qualifier of this member indicates this value is the clip space
+    //   position of the vertex wen this structure is returned from the vertex shader
+    float4 clipSpacePosition [[position]];
+
+    // Since this member does not have a special attribute qualifier, the rasterizer will
+    //   interpolate its value with values of other vertices making up the triangle and
+    //   pass that interpolated value to the fragment shader for each fragment in that triangle;
+    float2 textureCoordinate;
+
+} RasterizerData;
+
+// Vertex Function
+vertex RasterizerData
+vertexShader(uint vertexID [[ vertex_id ]],
+             constant UTMVertex *vertexArray [[ buffer(UTMVertexInputIndexVertices) ]],
+             constant vector_uint2 *viewportSizePointer  [[ buffer(UTMVertexInputIndexViewportSize) ]])
+
+{
+
+    RasterizerData out;
+
+    // Index into our array of positions to get the current vertex
+    //   Our positions are specified in pixel dimensions (i.e. a value of 100 is 100 pixels from
+    //   the origin)
+    float2 pixelSpacePosition = vertexArray[vertexID].position.xy;
+
+    // Get the size of the drawable so that we can convert to normalized device coordinates,
+    float2 viewportSize = float2(*viewportSizePointer);
+
+    // The output position of every vertex shader is in clip space (also known as normalized device
+    //   coordinate space, or NDC). A value of (-1.0, -1.0) in clip-space represents the
+    //   lower-left corner of the viewport whereas (1.0, 1.0) represents the upper-right corner of
+    //   the viewport.
+
+    // In order to convert from positions in pixel space to positions in clip space we divide the
+    //   pixel coordinates by half the size of the viewport.
+    out.clipSpacePosition.xy = pixelSpacePosition / (viewportSize / 2.0);
+
+    // Set the z component of our clip space position 0 (since we're only rendering in
+    //   2-Dimensions for this sample)
+    out.clipSpacePosition.z = 0.0;
+
+    // Set the w component to 1.0 since we don't need a perspective divide, which is also not
+    //   necessary when rendering in 2-Dimensions
+    out.clipSpacePosition.w = 1.0;
+
+    // Pass our input textureCoordinate straight to our output RasterizerData. This value will be
+    //   interpolated with the other textureCoordinate values in the vertices that make up the
+    //   triangle.
+    out.textureCoordinate = vertexArray[vertexID].textureCoordinate;
+    
+    return out;
+}
+
+// Fragment function
+fragment float4
+samplingShader(RasterizerData in [[stage_in]],
+               texture2d<half> colorTexture [[ texture(UTMTextureIndexBaseColor) ]])
+{
+    constexpr sampler textureSampler (mag_filter::linear,
+                                      min_filter::linear);
+
+    // Sample the texture to obtain a color
+    const half4 colorSample = colorTexture.sample(textureSampler, in.textureCoordinate);
+
+    // We return the color of the texture inverted
+    return float4(colorSample.argb);
+}
+

+ 22 - 0
UTM.xcodeproj/project.pbxproj

@@ -7,6 +7,8 @@
 	objects = {
 	objects = {
 
 
 /* Begin PBXBuildFile section */
 /* Begin PBXBuildFile section */
+		CE2D63D32265154700FC7E63 /* UTMRenderer.m in Sources */ = {isa = PBXBuildFile; fileRef = CE2D63CF2265154700FC7E63 /* UTMRenderer.m */; };
+		CE2D63D42265154700FC7E63 /* UTMShaders.metal in Sources */ = {isa = PBXBuildFile; fileRef = CE2D63D02265154700FC7E63 /* UTMShaders.metal */; };
 		CE31C245225E555600A965DD /* UTMConfiguration.m in Sources */ = {isa = PBXBuildFile; fileRef = CE31C244225E555600A965DD /* UTMConfiguration.m */; };
 		CE31C245225E555600A965DD /* UTMConfiguration.m in Sources */ = {isa = PBXBuildFile; fileRef = CE31C244225E555600A965DD /* UTMConfiguration.m */; };
 		CE31C24B225EA37400A965DD /* VMConfigViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = CE31C24A225EA37400A965DD /* VMConfigViewController.m */; };
 		CE31C24B225EA37400A965DD /* VMConfigViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = CE31C24A225EA37400A965DD /* VMConfigViewController.m */; };
 		CE31C24D225EA4A200A965DD /* VMConfigCreateViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = CE31C24C225EA4A200A965DD /* VMConfigCreateViewController.m */; };
 		CE31C24D225EA4A200A965DD /* VMConfigCreateViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = CE31C24C225EA4A200A965DD /* VMConfigCreateViewController.m */; };
@@ -36,6 +38,11 @@
 /* Begin PBXFileReference section */
 /* Begin PBXFileReference section */
 		CE2B89332262A21E00C6D9D8 /* UTMVirtualMachine.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = UTMVirtualMachine.h; sourceTree = "<group>"; };
 		CE2B89332262A21E00C6D9D8 /* UTMVirtualMachine.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = UTMVirtualMachine.h; sourceTree = "<group>"; };
 		CE2B89352262B2F600C6D9D8 /* UTMVirtualMachineDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = UTMVirtualMachineDelegate.h; sourceTree = "<group>"; };
 		CE2B89352262B2F600C6D9D8 /* UTMVirtualMachineDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = UTMVirtualMachineDelegate.h; sourceTree = "<group>"; };
+		CE2D63CF2265154700FC7E63 /* UTMRenderer.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = UTMRenderer.m; sourceTree = "<group>"; };
+		CE2D63D02265154700FC7E63 /* UTMShaders.metal */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.metal; path = UTMShaders.metal; sourceTree = "<group>"; };
+		CE2D63D12265154700FC7E63 /* UTMShaderTypes.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = UTMShaderTypes.h; sourceTree = "<group>"; };
+		CE2D63D22265154700FC7E63 /* UTMRenderer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = UTMRenderer.h; sourceTree = "<group>"; };
+		CE2D63D5226517D600FC7E63 /* UTMRendererDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = UTMRendererDelegate.h; sourceTree = "<group>"; };
 		CE31C243225E553500A965DD /* UTMConfiguration.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = UTMConfiguration.h; sourceTree = "<group>"; };
 		CE31C243225E553500A965DD /* UTMConfiguration.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = UTMConfiguration.h; sourceTree = "<group>"; };
 		CE31C244225E555600A965DD /* UTMConfiguration.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = UTMConfiguration.m; sourceTree = "<group>"; };
 		CE31C244225E555600A965DD /* UTMConfiguration.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = UTMConfiguration.m; sourceTree = "<group>"; };
 		CE31C246225E9FED00A965DD /* UTMConfigurationDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = UTMConfigurationDelegate.h; sourceTree = "<group>"; };
 		CE31C246225E9FED00A965DD /* UTMConfigurationDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = UTMConfigurationDelegate.h; sourceTree = "<group>"; };
@@ -96,6 +103,18 @@
 /* End PBXFrameworksBuildPhase section */
 /* End PBXFrameworksBuildPhase section */
 
 
 /* Begin PBXGroup section */
 /* Begin PBXGroup section */
+		CE2D63CE2265150F00FC7E63 /* Renderer */ = {
+			isa = PBXGroup;
+			children = (
+				CE2D63D22265154700FC7E63 /* UTMRenderer.h */,
+				CE2D63CF2265154700FC7E63 /* UTMRenderer.m */,
+				CE2D63D5226517D600FC7E63 /* UTMRendererDelegate.h */,
+				CE2D63D02265154700FC7E63 /* UTMShaders.metal */,
+				CE2D63D12265154700FC7E63 /* UTMShaderTypes.h */,
+			);
+			path = Renderer;
+			sourceTree = "<group>";
+		};
 		CE31C242225E543A00A965DD /* Configuration */ = {
 		CE31C242225E543A00A965DD /* Configuration */ = {
 			isa = PBXGroup;
 			isa = PBXGroup;
 			children = (
 			children = (
@@ -125,6 +144,7 @@
 				CE31C242225E543A00A965DD /* Configuration */,
 				CE31C242225E543A00A965DD /* Configuration */,
 				CE5E4954225C593C00148CEF /* ConfigurationViews */,
 				CE5E4954225C593C00148CEF /* ConfigurationViews */,
 				CE5F1659226138AB00F3D56B /* Managers */,
 				CE5F1659226138AB00F3D56B /* Managers */,
+				CE2D63CE2265150F00FC7E63 /* Renderer */,
 				CE7BED4D22600F5000A1E1B6 /* Views */,
 				CE7BED4D22600F5000A1E1B6 /* Views */,
 				CE550BCB225947990063E575 /* UTM */,
 				CE550BCB225947990063E575 /* UTM */,
 				CE550BCA225947990063E575 /* Products */,
 				CE550BCA225947990063E575 /* Products */,
@@ -304,8 +324,10 @@
 				CE31C245225E555600A965DD /* UTMConfiguration.m in Sources */,
 				CE31C245225E555600A965DD /* UTMConfiguration.m in Sources */,
 				CE7BED4C225FBB8600A1E1B6 /* VMConfigDrivesViewController.m in Sources */,
 				CE7BED4C225FBB8600A1E1B6 /* VMConfigDrivesViewController.m in Sources */,
 				CE74C28D225D88ED004E4FF1 /* VMConfigInputViewController.m in Sources */,
 				CE74C28D225D88ED004E4FF1 /* VMConfigInputViewController.m in Sources */,
+				CE2D63D42265154700FC7E63 /* UTMShaders.metal in Sources */,
 				CE74C28B225D88ED004E4FF1 /* VMConfigDisplayViewController.m in Sources */,
 				CE74C28B225D88ED004E4FF1 /* VMConfigDisplayViewController.m in Sources */,
 				CE4AA14C2264004F002E4A54 /* CSConnection.m in Sources */,
 				CE4AA14C2264004F002E4A54 /* CSConnection.m in Sources */,
+				CE2D63D32265154700FC7E63 /* UTMRenderer.m in Sources */,
 			);
 			);
 			runOnlyForDeploymentPostprocessing = 0;
 			runOnlyForDeploymentPostprocessing = 0;
 		};
 		};