Skip to content

Commit

Permalink
Initial Metal example
Browse files Browse the repository at this point in the history
  • Loading branch information
OneSadCookie authored and elmindreda committed Dec 19, 2016
1 parent 0f488ac commit 97b652c
Show file tree
Hide file tree
Showing 2 changed files with 175 additions and 0 deletions.
8 changes: 8 additions & 0 deletions examples/CMakeLists.txt
Expand Up @@ -41,6 +41,13 @@ add_executable(wave WIN32 MACOSX_BUNDLE wave.c ${ICON} ${GLAD})

target_link_libraries(particles "${CMAKE_THREAD_LIBS_INIT}" "${RT_LIBRARY}")

if (APPLE)
add_executable(metal MACOSX_BUNDLE metal.m ${ICON})
target_compile_options(metal PUBLIC "-fobjc-arc")
target_link_libraries(metal "-framework Metal" "-framework QuartzCore")
set_target_properties(metal PROPERTIES MACOSX_BUNDLE_BUNDLE_NAME "Metal")
endif()

set(WINDOWS_BINARIES boing gears heightmap particles simple splitview wave)
set(CONSOLE_BINARIES offscreen)

Expand All @@ -57,6 +64,7 @@ if (APPLE)
set_target_properties(boing PROPERTIES MACOSX_BUNDLE_BUNDLE_NAME "Boing")
set_target_properties(gears PROPERTIES MACOSX_BUNDLE_BUNDLE_NAME "Gears")
set_target_properties(heightmap PROPERTIES MACOSX_BUNDLE_BUNDLE_NAME "Heightmap")
set_target_properties(metal PROPERTIES MACOSX_BUNDLE_BUNDLE_NAME "Metal")
set_target_properties(particles PROPERTIES MACOSX_BUNDLE_BUNDLE_NAME "Particles")
set_target_properties(simple PROPERTIES MACOSX_BUNDLE_BUNDLE_NAME "Simple")
set_target_properties(splitview PROPERTIES MACOSX_BUNDLE_BUNDLE_NAME "SplitView")
Expand Down
167 changes: 167 additions & 0 deletions examples/metal.m
@@ -0,0 +1,167 @@
//========================================================================
// Simple GLFW+Metal example
// Copyright (c) Camilla Berglund <elmindreda@elmindreda.org>
//
// This software is provided 'as-is', without any express or implied
// warranty. In no event will the authors be held liable for any damages
// arising from the use of this software.
//
// Permission is granted to anyone to use this software for any purpose,
// including commercial applications, and to alter it and redistribute it
// freely, subject to the following restrictions:
//
// 1. The origin of this software must not be misrepresented; you must not
// claim that you wrote the original software. If you use this software
// in a product, an acknowledgment in the product documentation would
// be appreciated but is not required.
//
// 2. Altered source versions must be plainly marked as such, and must not
// be misrepresented as being the original software.
//
// 3. This notice may not be removed or altered from any source
// distribution.
//
//========================================================================
//! [code]

#import <Metal/Metal.h>
#import <QuartzCore/QuartzCore.h>
#import <simd/simd.h>

#define GLFW_INCLUDE_NONE
#import <GLFW/glfw3.h>
#define GLFW_EXPOSE_NATIVE_COCOA
#define GLFW_EXPOSE_NATIVE_NSGL
#import <GLFW/glfw3native.h>

#include <assert.h>
#include <stdlib.h>
#include <stdio.h>

static void error_callback(int error, const char* description)
{
fputs(description, stderr);
}

static void key_callback(GLFWwindow* window, int key, int scancode, int action, int mods)
{
if (key == GLFW_KEY_ESCAPE && action == GLFW_PRESS)
glfwSetWindowShouldClose(window, GLFW_TRUE);
}

int main(void)
{
id<MTLDevice> device = MTLCreateSystemDefaultDevice();
if (!device)
{
exit(EXIT_FAILURE);
}

GLFWwindow* window;

glfwSetErrorCallback(error_callback);

if (!glfwInit())
exit(EXIT_FAILURE);

glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API);
window = glfwCreateWindow(640, 480, "Metal Example", NULL, NULL);
if (!window)
{
glfwTerminate();
exit(EXIT_FAILURE);
}

NSWindow *nswin = glfwGetCocoaWindow(window);
CAMetalLayer *layer = [CAMetalLayer layer];
layer.device = device;
layer.pixelFormat = MTLPixelFormatBGRA8Unorm;

This comment has been minimized.

Copy link
@dmitshur

dmitshur Oct 21, 2018

Collaborator

FWIW, according to https://developer.apple.com/documentation/quartzcore/cametallayer/1478155-pixelformat:

The default value is MTLPixelFormatBGRA8Unorm.

So this line is a no-op.

nswin.contentView.layer = layer;
nswin.contentView.wantsLayer = YES;

This comment has been minimized.

Copy link
@dmitshur

dmitshur Oct 21, 2018

Collaborator

The default value here is NO so setting it to YES has an effect. However, I haven't been able to spot any differences in behavior regardless if wantsLayer is set to YES or left at NO.

According to https://developer.apple.com/documentation/appkit/nsview/1483695-wantslayer, it should have a pretty significant effect:

Setting the value of this property to YES turns the view into a layer-backed view—that is, the view uses a CALayer object to manage its rendered content. Creating a layer-backed view implicitly causes the entire view hierarchy under that view to become layer-backed. Thus, the view and all of its subviews (including subviews of subviews) become layer-backed. The default value of this property is NO.

Do you have any ideas why its value doesn't seem to matter? What was your motivation for setting it to YES? /cc @OneSadCookie

This comment has been minimized.

Copy link
@meshula

meshula Oct 21, 2018

https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/CoreAnimation_guide/ImprovingAnimationPerformance/ImprovingAnimationPerformance.html

layer backing causes caching. It should be a user option as it is sometimes necessary, but it also impacts performance negatively.

This comment has been minimized.

Copy link
@dmitshur

dmitshur Oct 21, 2018

Collaborator

I see, thanks. Reading the rest of the documentation I linked above has some very relevant sections:

In addition to creating a layer-backed view, you can create a layer-hosting view by assigning a layer directly to the view’s layer property. In a layer-hosting view, you are responsible for managing the view’s layer. To create a layer-hosting view, you must set the layer property first and then set this property to YES. The order in which you set the values of these properties is crucial.

Emphasis mine.

This comment has been minimized.

Copy link
@OneSadCookie

OneSadCookie Oct 22, 2018

Author Contributor

My recollection is that I found exactly this sentence in the documentation, and that it didn't work until I followed it.

I believe recent versions of macOS (maybe only Mojave? I'm a bit out of touch) now layer-back all views, so it's possible this is no longer required if your minimum deployment target is high enough.


MTLCompileOptions *compileOptions = [MTLCompileOptions new];
compileOptions.languageVersion = MTLLanguageVersion1_1;
NSError *compileError;
id<MTLLibrary> lib = [device newLibraryWithSource:
@"#include <metal_stdlib>\n"
"using namespace metal;\n"
"vertex float4 v_simple(\n"
" constant float4 *in [[buffer(0)]],\n"
" uint vid [[vertex_id]])\n"
"{\n"
" return in[vid];\n"
"}\n"
"fragment float4 f_simple(\n"
" float4 in [[stage_in]])\n"
"{\n"
" return float4(1, 0, 0, 1);\n"
"}\n"
options:compileOptions error:&compileError];
if (!lib)
{
NSLog(@"can't create library: %@", compileError);
glfwTerminate();
exit(EXIT_FAILURE);
}

id<MTLFunction> vs = [lib newFunctionWithName:@"v_simple"];
assert(vs);
id<MTLFunction> fs = [lib newFunctionWithName:@"f_simple"];
assert(fs);

id<MTLCommandQueue> cq = [device newCommandQueue];
assert(cq);

MTLRenderPipelineDescriptor *rpd = [MTLRenderPipelineDescriptor new];
rpd.vertexFunction = vs;
rpd.fragmentFunction = fs;
rpd.colorAttachments[0].pixelFormat = layer.pixelFormat;
id<MTLRenderPipelineState> rps = [device newRenderPipelineStateWithDescriptor:rpd error:NULL];
assert(rps);

glfwSetKeyCallback(window, key_callback);

while (!glfwWindowShouldClose(window))
{
float ratio;
int width, height;

glfwGetFramebufferSize(window, &width, &height);
ratio = width / (float) height;

layer.drawableSize = CGSizeMake(width, height);
id<CAMetalDrawable> drawable = [layer nextDrawable];
assert(drawable);

id<MTLCommandBuffer> cb = [cq commandBuffer];

MTLRenderPassDescriptor *rpd = [MTLRenderPassDescriptor new];
MTLRenderPassColorAttachmentDescriptor *cd = rpd.colorAttachments[0];
cd.texture = drawable.texture;
cd.loadAction = MTLLoadActionClear;
cd.clearColor = MTLClearColorMake(1.0, 1.0, 1.0, 1.0);
cd.storeAction = MTLStoreActionStore;
id<MTLRenderCommandEncoder> rce = [cb renderCommandEncoderWithDescriptor:rpd];

[rce setRenderPipelineState:rps];
[rce setVertexBytes:(vector_float4[]){
{ 0, 0, 0, 1 },
{ -1, 1, 0, 1 },
{ 1, 1, 0, 1 },
} length:3 * sizeof(vector_float4) atIndex:0];
[rce drawPrimitives:MTLPrimitiveTypeTriangle vertexStart:0 vertexCount:3];

[rce endEncoding];
[cb presentDrawable:drawable];
[cb commit];

glfwPollEvents();
}

glfwDestroyWindow(window);

glfwTerminate();
exit(EXIT_SUCCESS);
}

//! [code]

0 comments on commit 97b652c

Please sign in to comment.