{"id":19602631,"url":"https://github.com/wang-bin/hellotriangle","last_synced_at":"2026-02-04T01:07:59.559Z","repository":{"id":141348744,"uuid":"266482572","full_name":"wang-bin/HelloTriangle","owner":"wang-bin","description":"Apple Metal example","archived":false,"fork":false,"pushed_at":"2020-11-22T14:22:44.000Z","size":171,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-02-26T15:45:34.013Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Objective-C++","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/wang-bin.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE/LICENSE.txt","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2020-05-24T06:21:06.000Z","updated_at":"2020-11-22T14:22:46.000Z","dependencies_parsed_at":null,"dependency_job_id":"ab43b7d2-493c-4807-be84-adb698a2899a","html_url":"https://github.com/wang-bin/HelloTriangle","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/wang-bin/HelloTriangle","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/wang-bin%2FHelloTriangle","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/wang-bin%2FHelloTriangle/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/wang-bin%2FHelloTriangle/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/wang-bin%2FHelloTriangle/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/wang-bin","download_url":"https://codeload.github.com/wang-bin/HelloTriangle/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/wang-bin%2FHelloTriangle/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":260182899,"owners_count":22971194,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-11-11T09:25:09.308Z","updated_at":"2026-02-04T01:07:59.530Z","avatar_url":"https://github.com/wang-bin.png","language":"Objective-C++","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Using a Render Pipeline to Render Primitives\n\nRender a simple 2D triangle.\n\n## Overview\n\nIn [Using Metal to Draw a View’s Contents](https://developer.apple.com/documentation/metal/basic_tasks_and_concepts/using_metal_to_draw_a_view_s_contents), you learned how to set up an `MTKView` object and to change the view's contents using a render pass.\nThat sample simply erased the view's contents to a background color.\nThis sample shows you how to configure a render pipeline and use it as part of the render pass to draw a simple 2D colored triangle into the view.\nThe sample supplies a position and color for each vertex, and the render pipeline uses that data to render the triangle, interpolating color values between the colors specified for the triangle's vertices.\n\n![Simple 2D Triangle Vertices](Documentation/2DTriangleVertices.png)\n\n- Note: The Xcode project contains schemes for running the sample on macOS, iOS, and tvOS.\nMetal isn't supported in the iOS or tvOS Simulator, so the iOS and tvOS schemes require a physical device to run the sample.\nThe default scheme is macOS.\n\n## Understand the Metal Render Pipeline\n\nA *render pipeline* processes drawing commands and writes data into a render pass’s targets.\n A render pipeline has many stages, some programmed using shaders and others with fixed or configurable behavior.\nThis sample focuses on the three main stages of the pipeline: the vertex stage, the rasterization stage, and the fragment stage.\nThe vertex stage and fragment stage are programmable, so you write functions for them in Metal Shading Language (MSL).\nThe rasterization stage has fixed behavior.\n\n**Figure 1** Main stages of the Metal graphics render pipeline\n![Main Stages of the Metal Graphics Render Pipeline](Documentation/SimplePipeline.png)\n\nRendering starts with a drawing command, which includes a vertex count and what kind of primitive to render. For example, here's the drawing command from this sample:\n\n``` objective-c\n// Draw the triangle.\n[renderEncoder drawPrimitives:MTLPrimitiveTypeTriangle\n                  vertexStart:0\n                  vertexCount:3];\n```\n\nThe vertex stage provides data for each vertex. When enough vertices have been processed, the render pipeline rasterizes the primitive, determining which pixels in the render targets lie within the boundaries of the primitive. The fragment stage determines the values to write into the render targets for those pixels.\n\nIn the rest of this sample, you will see how to write the vertex and fragment functions, how to create the render pipeline state object, and finally, how to encode a draw command that uses this pipeline.\n\n## Decide How Data is Processed by Your Custom Render Pipeline\n\nA vertex function generates data for a single vertex and a fragment function generates data for a single fragment, but you decide how they work.\nYou configure the stages of the pipeline with a goal in mind, meaning that you know what you want the pipeline to generate and how it generates those results.\n\nDecide what data to pass into your render pipeline and what data is passed to later stages of the pipeline. There are typically three places where you do this:\n\n- The inputs to the pipeline, which are provided by your app and passed to the vertex stage.\n- The outputs of the vertex stage, which is passed to the rasterization stage.\n- The inputs to the fragment stage, which are provided by your app or generated by the rasterization stage.\n\nIn this sample, the input data for the pipeline is the position of a vertex and its color. To demonstrate the kind of transformation you typically perform in a vertex function, input coordinates are defined in a custom coordinate space, measured in pixels from the center of the view. These coordinates need to be translated into Metal's coordinate system.\n\nDeclare an `AAPLVertex` structure, using SIMD vector types to hold the position and color data.\nTo share a single definition for how the structure is laid out in memory, declare the structure in a common header and import it in both the Metal shader and the app.\n\n``` objective-c\ntypedef struct\n{\n    vector_float2 position;\n    vector_float4 color;\n} AAPLVertex;\n```\n\nSIMD types are commonplace in Metal Shading Language, and you should also use them in your app using the simd library.\nSIMD types contain multiple channels of a particular data type, so declaring the position as a `vector_float2` means it contains two 32-bit float values (which will hold the x and y coordinates.)\nColors are stored using a `vector_float4`, so they have four channels – red, green, blue, and alpha.\n\nIn the app, the input data is specified using a constant array:\n\n``` objective-c\nstatic const AAPLVertex triangleVertices[] =\n{\n    // 2D positions,    RGBA colors\n    { {  250,  -250 }, { 1, 0, 0, 1 } },\n    { { -250,  -250 }, { 0, 1, 0, 1 } },\n    { {    0,   250 }, { 0, 0, 1, 1 } },\n};\n```\n\nThe vertex stage generates data for a vertex, so it needs to provide a color and a transformed position.\nDeclare a `RasterizerData` structure containing a position and a color value, again using SIMD types. \n\n``` metal\ntypedef struct\n{\n    // The [[position]] attribute of this member indicates that this value\n    // is the clip space position of the vertex when this structure is\n    // returned from the vertex function.\n    float4 position [[position]];\n\n    // Since this member does not have a special attribute, the rasterizer\n    // interpolates its value with the values of the other triangle vertices\n    // and then passes the interpolated value to the fragment shader for each\n    // fragment in the triangle.\n    float4 color;\n\n} RasterizerData;\n```\n\nThe output position (described in detail below) must be defined as a `vector_float4`. The color is declared as it was in the input data structure.\n\nYou need to tell Metal which field in the rasterization data provides position data, because Metal doesn't enforce any particular naming convention for fields in your struct.\nAnnotate the `position` field with the `[[position]]` attribute qualifier to declare that this field holds the output position.\n\nThe fragment function simply passes the rasterization stage's data to later stages so it doesn't need any additional arguments.\n\n## Declare the Vertex Function\n\nDeclare the vertex function, including its input arguments and the data it outputs.\nMuch like compute functions were declared using the `kernel` keyword, you declare a vertex function using the `vertex` keyword.\n\n``` metal\nvertex RasterizerData\nvertexShader(uint vertexID [[vertex_id]],\n             constant AAPLVertex *vertices [[buffer(AAPLVertexInputIndexVertices)]],\n             constant vector_uint2 *viewportSizePointer [[buffer(AAPLVertexInputIndexViewportSize)]])\n```\n\nThe first argument, `vertexID`, uses the `[[vertex_id]]` attribute qualifier, which is another Metal keyword.\nWhen you execute a render command, the GPU calls your vertex function multiple times, generating a unique value for each vertex.\n\nThe second argument, `vertices`, is an array that contains the vertex data, using the `AAPLVertex` struct previously defined.\n\nTo transform the position into Metal's coordinates, the function needs the size of the viewport (in pixels) that the triangle is being drawn into, so this is stored in the `viewportSizePointer` argument.\n\nThe second and third arguments have the `[[buffer(n)]]` attribute qualifier.\nBy default, Metal assigns slots in the argument table for each parameter automatically.\nWhen you add the `[[buffer(n)]]` qualifier to a buffer argument, you tell Metal explicitly which slot to use.\nDeclaring slots explicitly can make it easier to revise your shaders without also needing to change your app code.\nDeclare the constants for the two indicies in the shared header file.\n\nThe function's output is a `RasterizerData` struct.\n\n## Write the Vertex Function\n\nYour vertex function must generate both fields of the output struct. \nUse the `vertexID` argument to index into the `vertices` array and read the input data for the vertex.\nAlso, retrieve the viewport dimensions.\n\n``` metal\nfloat2 pixelSpacePosition = vertices[vertexID].position.xy;\n\n// Get the viewport size and cast to float.\nvector_float2 viewportSize = vector_float2(*viewportSizePointer);\n\n```\n\nVertex functions must provide position data in *clip-space coordinates*, which are 3D points specified using a four-dimensional homogenous vector (`x,y,z,w`). The rasterization stage takes the output position and divides the `x`,`y`, and `z` coordinates by `w` to generate a 3D point in *normalized device coordinates*. Normalized device coordinates are independent of viewport size.\n\n**Figure 2** Normalized device coordinate system\n![Normalized device coordinate system](Documentation/normalizeddevicecoords.png)\n\nNormalized device coordinates use a *left-handed coordinate system* and map to positions in the viewport.\nPrimitives are clipped to a box in this coordinate system and then rasterized.\nThe lower-left corner of the clipping box is at an `(x,y)` coordinate of `(-1.0,-1.0)` and the upper-right corner is at `(1.0,1.0)`.\nPositive-z values point away from the camera (into the screen.)\nThe visible portion of the `z` coordinate is between `0.0` (the near clipping plane) and `1.0` (the far clipping plane).\n\nTransform the input coordinate system to the normalized device coordinate system.\n\n![Vertex function coordinate transformation](Documentation/metal-coordinate-transformation.png)\n\nBecause this is a 2D application and does not need homogenous coordinates, first write a default value to the output coordinate, with the the `w` value is set to `1.0` and the other coordinates set to `0.0`.\nThis means that the coordinates are already in the normalized device coordinate space and the vertex function should generate (x,y) coordinates in that coordinate space.\nDivide the input position by half the viewport size to generate normalized device coordinates.\nSince this calculation is performed using SIMD types, both channels can be divided at the same time using a single line of code.\nPerform the divide and put the results in the x and y channels of the output position. \n\n``` metal\nout.position = vector_float4(0.0, 0.0, 0.0, 1.0);\nout.position.xy = pixelSpacePosition / (viewportSize / 2.0);\n```\n\nFinally, copy the color value into the `out.color` return value.\n\n``` metal\nout.color = vertices[vertexID].color;\n```\n\n## Write a Fragment Function\n\nA *fragment* is a possible change to the render targets. The rasterizer determines which pixels of the render target are covered by the primitive.\nOnly fragments whose pixel centers are inside the triangle are rendered.\n\n**Figure 3** Fragments generated by the rasterization stage\n![Fragments generated by the rasterization stage](Documentation/Rasterization.png)\n\nA fragment function processes incoming information from the rasterizer for a single position and calculates output values for each of the render targets. These fragment values are processed by later stages in the pipeline, eventually being written to the render targets.\n\n- Note: The reason a fragment is called a possible change is because the pipeline stages after the fragment stage can be configured to reject some fragments or change what gets written to the render targets. In this sample, all values calculated by the fragment stage are written as-is to the render target.\n\nThe fragment shader in this sample receives the same parameters that were declared in the vertex shader's output. Declare the fragment function using the `fragment` keyword. It takes a single argument, the same `RasterizerData` structure that was provided by the vertex stage. Add the `[[stage_in]]` attribute qualifier to indicate that this argument is generated by the rasterizer.\n\n``` metal\nfragment float4 fragmentShader(RasterizerData in [[stage_in]])\n```\n\nIf your fragment function writes to multiple render targets, it must declare a struct with fields for each render target.\nBecause this sample only has a single render target, you specify a floating-point vector directly as the function's output. This output is the color to be written to the render target.\n\nThe rasterization stage calculates values for each fragment's arguments and calls the fragment function with them.\nThe rasterization stage calculates its color argument as a blend of the colors at the triangle's vertices.\nThe closer a fragment is to a vertex, the more that vertex contributes to the final color.\n\n**Figure 4** Interpolated fragment colors\n![Interpolated Fragment Colors](Documentation/Interpolation.png)\n\nReturn the interpolated color as the function's output. \n\n``` metal\nreturn in.color;\n```\n\n## Create a Render Pipeline State Object\n\nNow that the functions are complete, you can create a render pipeline that uses them.\nFirst, get the default library and obtain a [`MTLFunction`][MTLFunction] object for each function.\n\n``` objective-c\nid\u003cMTLLibrary\u003e defaultLibrary = [_device newDefaultLibrary];\n\nid\u003cMTLFunction\u003e vertexFunction = [defaultLibrary newFunctionWithName:@\"vertexShader\"];\nid\u003cMTLFunction\u003e fragmentFunction = [defaultLibrary newFunctionWithName:@\"fragmentShader\"];\n```\n\nNext, create a [`MTLRenderPipelineState`][MTLRenderPipelineState] object.\nRender pipelines have more stages to configure, so you use a [`MTLRenderPipelineDescriptor`][MTLRenderPipelineDescriptor] to configure the pipeline.\n\n``` objective-c\nMTLRenderPipelineDescriptor *pipelineStateDescriptor = [[MTLRenderPipelineDescriptor alloc] init];\npipelineStateDescriptor.label = @\"Simple Pipeline\";\npipelineStateDescriptor.vertexFunction = vertexFunction;\npipelineStateDescriptor.fragmentFunction = fragmentFunction;\npipelineStateDescriptor.colorAttachments[0].pixelFormat = mtkView.colorPixelFormat;\n\n_pipelineState = [_device newRenderPipelineStateWithDescriptor:pipelineStateDescriptor\n                                                         error:\u0026error];\n```\n\nIn addition to specifying the vertex and fragment functions, you also declare the *pixel format* of all render targets that the pipeline will draw into.\nA pixel format ([`MTLPixelFormat`][MTLPixelFormat]) defines the memory layout of pixel data.\nFor simple formats, this definition includes the number of bytes per pixel, the number of channels of data stored in a pixel, and the bit layout of those channels.\nSince this sample only has one render target and it is provided by the view, copy the view's pixel format into the render pipeline descriptor.\nYour render pipeline state must use a pixel format that is compatible with the one specified by the render pass.\nIn this sample, the render pass and the pipeline state object both use the view's pixel format, so they are always the same.\n\nWhen Metal creates the render pipeline state object, the pipeline is configured to convert the fragment function's output into the render target's pixel format.\nIf you want to target a different pixel format, you need to create a different pipeline state object.\nYou can reuse the same shaders in multiple pipelines targeting different pixel formats.\n\n \n## Set a Viewport\n\nNow that you have the render pipeline state object for the pipeline, you'll render the triangle. You do this using a render command encoder. First, set the viewport, so that Metal knows which part of the render target you want to draw into.\n\n``` objective-c\n// Set the region of the drawable to draw into.\n[renderEncoder setViewport:(MTLViewport){0.0, 0.0, _viewportSize.x, _viewportSize.y, 0.0, 1.0 }];\n```\n\n## Set the Render Pipeline State\n\nSet the render pipeline state for the pipeline you want to use.\n\n``` objective-c\n[renderEncoder setRenderPipelineState:_pipelineState];\n```\n\n## Send Argument Data to the Vertex Function\n\nOften, you use buffers ([`MTLBuffer`][MTLBuffer]) to pass data to shaders.\nHowever, when you need to pass only a small amount of data to the vertex function, as is the case here, copy the data directly into the command buffer.\n\nThe sample copies data for both parameters into the command buffer.\nThe vertex data is copied from an array defined in the sample.\nThe viewport data is copied from the same variable that you used to set the viewport. \n\nIn this sample, the fragment function uses only the data it receives from the rasterizer, so there are no arguments to set.\n\n``` objective-c\n[renderEncoder setVertexBytes:triangleVertices\n                       length:sizeof(triangleVertices)\n                      atIndex:AAPLVertexInputIndexVertices];\n\n[renderEncoder setVertexBytes:\u0026_viewportSize\n                       length:sizeof(_viewportSize)\n                      atIndex:AAPLVertexInputIndexViewportSize];\n```\n\n\n## Encode the Drawing Command\n\nSpecify the kind of primitive, the starting index, and the number of vertices.\nWhen the triangle is rendered, the vertex function is called with values of 0, 1, and 2 for the `vertexID` argument.\n\n``` objective-c\n// Draw the triangle.\n[renderEncoder drawPrimitives:MTLPrimitiveTypeTriangle\n                  vertexStart:0\n                  vertexCount:3];\n```\n\nAs with Drawing to the Screen Using Metal, you end the encoding process and commit the command buffer.\nHowever, you could encode more render commands using the same set of steps.\nThe final image is rendered as if the commands were processed in the order they were specified.\n(For performance, the GPU is allowed to process commands or even parts of commands in parallel, so long as the final result appears to have been rendered in order. )\n\n## Experiment with the Color Interpolation\n\nIn this sample, color values were interpolated across the triangle.\nThat's often what you want, but sometimes you want a value to be generated by one vertex and remain constant across the whole primitive.\nSpecify the `flat` attribute qualifier on an output of the vertex function to do this.\nTry this now.\nFind the definition of `RasterizerData` in the sample project and add the `[[flat]]` qualifier to its `color` field.\n\n`float4 color [[flat]];`\n\nRun the sample again.\nThe render pipeline uses the color value from the first vertex (called the *provoking vertex*) uniformly across the triangle, and it ignores the colors from the other two vertices.\nYou can use a mix of flat shaded and interpolated values, simply by adding or omitting the `flat` qualifier on your vertex function's outputs.\nThe [Metal Shading Language specification][ShadingLanguageSpec] defines other attribute qualifiers you can also use to modify the rasterization behavior.\n\n[ScreenDrawing]: https://developer.apple.com/documentation/metal\n[MTLDevice]: https://developer.apple.com/documentation/metal/mtldevice\n[MTLResource]: https://developer.apple.com/documentation/metal/mtlresource\n[MTLBuffer]: https://developer.apple.com/documentation/metal/mtlbuffer\n[MTLRenderPipelineState]: https://developer.apple.com/documentation/metal/mtlrenderpipelinestate\n[MTLRenderPipelineDescriptor]: https://developer.apple.com/documentation/metal/mtlrenderpipelinedescriptor\n[MTLRenderCommandEncoder]: https://developer.apple.com/documentation/metal/mtlrendercommandencoder\n[MTLPixelFormat]: https://developer.apple.com/documentation/metal/mtlpixelformat\n[MTKView]: https://developer.apple.com/documentation/metalkit/mtkview\n[ShadingLanguageSpec]: https://developer.apple.com/metal/Metal-Shading-Language-Specification.pdf\n[MTLFunction]: https://developer.apple.com/documentation/metal/mtlfunction\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fwang-bin%2Fhellotriangle","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fwang-bin%2Fhellotriangle","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fwang-bin%2Fhellotriangle/lists"}