/* * * Copyright 2022 Apple Inc. * * 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. */ #include #define NS_PRIVATE_IMPLEMENTATION #define MTL_PRIVATE_IMPLEMENTATION #define MTK_PRIVATE_IMPLEMENTATION #define CA_PRIVATE_IMPLEMENTATION #include #include #include #include #pragma region Declarations { class Renderer { public: Renderer( MTL::Device* pDevice ); ~Renderer(); void buildShaders(); void buildBuffers(); void draw( MTK::View* pView ); private: MTL::Device* _pDevice; MTL::CommandQueue* _pCommandQueue; MTL::RenderPipelineState* _pPSO; MTL::Buffer* _pVertexPositionsBuffer; MTL::Buffer* _pVertexColorsBuffer; }; class MyMTKViewDelegate : public MTK::ViewDelegate { public: MyMTKViewDelegate( MTL::Device* pDevice ); virtual ~MyMTKViewDelegate() override; virtual void drawInMTKView( MTK::View* pView ) override; private: Renderer* _pRenderer; }; class MyAppDelegate : public NS::ApplicationDelegate { public: ~MyAppDelegate(); NS::Menu* createMenuBar(); virtual void applicationWillFinishLaunching( NS::Notification* pNotification ) override; virtual void applicationDidFinishLaunching( NS::Notification* pNotification ) override; virtual bool applicationShouldTerminateAfterLastWindowClosed( NS::Application* pSender ) override; private: NS::Window* _pWindow; MTK::View* _pMtkView; MTL::Device* _pDevice; MyMTKViewDelegate* _pViewDelegate = nullptr; }; #pragma endregion Declarations } int main( int argc, char* argv[] ) { NS::AutoreleasePool* pAutoreleasePool = NS::AutoreleasePool::alloc()->init(); MyAppDelegate del; NS::Application* pSharedApplication = NS::Application::sharedApplication(); pSharedApplication->setDelegate( &del ); pSharedApplication->run(); pAutoreleasePool->release(); return 0; } #pragma mark - AppDelegate #pragma region AppDelegate { MyAppDelegate::~MyAppDelegate() { _pMtkView->release(); _pWindow->release(); _pDevice->release(); delete _pViewDelegate; } NS::Menu* MyAppDelegate::createMenuBar() { using NS::StringEncoding::UTF8StringEncoding; NS::Menu* pMainMenu = NS::Menu::alloc()->init(); NS::MenuItem* pAppMenuItem = NS::MenuItem::alloc()->init(); NS::Menu* pAppMenu = NS::Menu::alloc()->init( NS::String::string( "Appname", UTF8StringEncoding ) ); NS::String* appName = NS::RunningApplication::currentApplication()->localizedName(); NS::String* quitItemName = NS::String::string( "Quit ", UTF8StringEncoding )->stringByAppendingString( appName ); SEL quitCb = NS::MenuItem::registerActionCallback( "appQuit", [](void*,SEL,const NS::Object* pSender){ auto pApp = NS::Application::sharedApplication(); pApp->terminate( pSender ); } ); NS::MenuItem* pAppQuitItem = pAppMenu->addItem( quitItemName, quitCb, NS::String::string( "q", UTF8StringEncoding ) ); pAppQuitItem->setKeyEquivalentModifierMask( NS::EventModifierFlagCommand ); pAppMenuItem->setSubmenu( pAppMenu ); NS::MenuItem* pWindowMenuItem = NS::MenuItem::alloc()->init(); NS::Menu* pWindowMenu = NS::Menu::alloc()->init( NS::String::string( "Window", UTF8StringEncoding ) ); SEL closeWindowCb = NS::MenuItem::registerActionCallback( "windowClose", [](void*, SEL, const NS::Object*){ auto pApp = NS::Application::sharedApplication(); pApp->windows()->object< NS::Window >(0)->close(); } ); NS::MenuItem* pCloseWindowItem = pWindowMenu->addItem( NS::String::string( "Close Window", UTF8StringEncoding ), closeWindowCb, NS::String::string( "w", UTF8StringEncoding ) ); pCloseWindowItem->setKeyEquivalentModifierMask( NS::EventModifierFlagCommand ); pWindowMenuItem->setSubmenu( pWindowMenu ); pMainMenu->addItem( pAppMenuItem ); pMainMenu->addItem( pWindowMenuItem ); pAppMenuItem->release(); pWindowMenuItem->release(); pAppMenu->release(); pWindowMenu->release(); return pMainMenu->autorelease(); } void MyAppDelegate::applicationWillFinishLaunching( NS::Notification* pNotification ) { NS::Menu* pMenu = createMenuBar(); NS::Application* pApp = reinterpret_cast< NS::Application* >( pNotification->object() ); pApp->setMainMenu( pMenu ); pApp->setActivationPolicy( NS::ActivationPolicy::ActivationPolicyRegular ); } void MyAppDelegate::applicationDidFinishLaunching( NS::Notification* pNotification ) { CGRect frame = (CGRect){ {100.0, 100.0}, {512.0, 512.0} }; _pWindow = NS::Window::alloc()->init( frame, NS::WindowStyleMaskClosable|NS::WindowStyleMaskTitled, NS::BackingStoreBuffered, false ); _pDevice = MTL::CreateSystemDefaultDevice(); _pMtkView = MTK::View::alloc()->init( frame, _pDevice ); _pMtkView->setColorPixelFormat( MTL::PixelFormat::PixelFormatBGRA8Unorm_sRGB ); _pMtkView->setClearColor( MTL::ClearColor::Make( 1.0, 0.0, 0.0, 1.0 ) ); _pViewDelegate = new MyMTKViewDelegate( _pDevice ); _pMtkView->setDelegate( _pViewDelegate ); _pWindow->setContentView( _pMtkView ); _pWindow->setTitle( NS::String::string( "01 - Primitive", NS::StringEncoding::UTF8StringEncoding ) ); _pWindow->makeKeyAndOrderFront( nullptr ); NS::Application* pApp = reinterpret_cast< NS::Application* >( pNotification->object() ); pApp->activateIgnoringOtherApps( true ); } bool MyAppDelegate::applicationShouldTerminateAfterLastWindowClosed( NS::Application* pSender ) { return true; } #pragma endregion AppDelegate } #pragma mark - ViewDelegate #pragma region ViewDelegate { MyMTKViewDelegate::MyMTKViewDelegate( MTL::Device* pDevice ) : MTK::ViewDelegate() , _pRenderer( new Renderer( pDevice ) ) { } MyMTKViewDelegate::~MyMTKViewDelegate() { delete _pRenderer; } void MyMTKViewDelegate::drawInMTKView( MTK::View* pView ) { _pRenderer->draw( pView ); } #pragma endregion ViewDelegate } #pragma mark - Renderer #pragma region Renderer { Renderer::Renderer( MTL::Device* pDevice ) : _pDevice( pDevice->retain() ) { _pCommandQueue = _pDevice->newCommandQueue(); buildShaders(); buildBuffers(); } Renderer::~Renderer() { _pVertexPositionsBuffer->release(); _pVertexColorsBuffer->release(); _pPSO->release(); _pCommandQueue->release(); _pDevice->release(); } void Renderer::buildShaders() { using NS::StringEncoding::UTF8StringEncoding; const char* shaderSrc = R"( #include using namespace metal; constant float2 myGlobalPositions[3] = { float2(-0.8, 0.8), float2(0.0,-0.8), float2(0.8,0.8) }; constant float3 myGlobalColors[3] = { float3(1.0, 0.3, 0.2), float3(0.8, 1.0, 0.0), float3(0.8, 0.0, 1.0) }; struct v2f { float4 position [[position]]; float3 color; }; v2f vertex vertexMain( uint vertexId [[vertex_id]], device const float3* positions [[buffer(0)]], device const float3* colors [[buffer(1)]] ) { v2f o; // This uses neither of the global const arrays. It works as expected. // o.position = float4( positions[ vertexId ], 1.0 ); // o.color = colors[ vertexId ]; // This does not use myGlobalPositions. It works as expected. // o.position = float4( positions[ vertexId ], 1.0 ); // o.color = myGlobalColors[vertexId]; // This uses myGlobalPositions and myGlobalColors. It does NOT WORK AS EXPECTED. o.position = float4( myGlobalPositions[vertexId], 0.0, 1.0); o.color = myGlobalColors[vertexId]; return o; } float4 fragment fragmentMain( v2f in [[stage_in]] ) { return float4( in.color, 1.0 ); } )"; NS::Error* pError = nullptr; MTL::Library* pLibrary = _pDevice->newLibrary( NS::String::string(shaderSrc, UTF8StringEncoding), nullptr, &pError ); if ( !pLibrary ) { __builtin_printf( "%s", pError->localizedDescription()->utf8String() ); assert( false ); } MTL::Function* pVertexFn = pLibrary->newFunction( NS::String::string("vertexMain", UTF8StringEncoding) ); MTL::Function* pFragFn = pLibrary->newFunction( NS::String::string("fragmentMain", UTF8StringEncoding) ); MTL::RenderPipelineDescriptor* pDesc = MTL::RenderPipelineDescriptor::alloc()->init(); pDesc->setVertexFunction( pVertexFn ); pDesc->setFragmentFunction( pFragFn ); pDesc->colorAttachments()->object(0)->setPixelFormat( MTL::PixelFormat::PixelFormatBGRA8Unorm_sRGB ); _pPSO = _pDevice->newRenderPipelineState( pDesc, &pError ); if ( !_pPSO ) { __builtin_printf( "%s", pError->localizedDescription()->utf8String() ); assert( false ); } pVertexFn->release(); pFragFn->release(); pDesc->release(); pLibrary->release(); } void Renderer::buildBuffers() { const size_t NumVertices = 3; simd::float3 positions[NumVertices] = { { -0.8f, 0.8f, 0.0f }, { 0.0f, -0.8f, 0.0f }, { +0.8f, 0.8f, 0.0f } }; simd::float3 colors[NumVertices] = { { 1.0, 0.3f, 0.2f }, { 0.8f, 1.0, 0.0f }, { 0.8f, 0.0f, 1.0 } }; const size_t positionsDataSize = NumVertices * sizeof( simd::float3 ); const size_t colorDataSize = NumVertices * sizeof( simd::float3 ); MTL::Buffer* pVertexPositionsBuffer = _pDevice->newBuffer( positionsDataSize, MTL::ResourceStorageModeManaged ); MTL::Buffer* pVertexColorsBuffer = _pDevice->newBuffer( colorDataSize, MTL::ResourceStorageModeManaged ); _pVertexPositionsBuffer = pVertexPositionsBuffer; _pVertexColorsBuffer = pVertexColorsBuffer; memcpy( _pVertexPositionsBuffer->contents(), positions, positionsDataSize ); memcpy( _pVertexColorsBuffer->contents(), colors, colorDataSize ); _pVertexPositionsBuffer->didModifyRange( NS::Range::Make( 0, _pVertexPositionsBuffer->length() ) ); _pVertexColorsBuffer->didModifyRange( NS::Range::Make( 0, _pVertexColorsBuffer->length() ) ); } void Renderer::draw( MTK::View* pView ) { NS::AutoreleasePool* pPool = NS::AutoreleasePool::alloc()->init(); MTL::CommandBuffer* pCmd = _pCommandQueue->commandBuffer(); MTL::RenderPassDescriptor* pRpd = pView->currentRenderPassDescriptor(); MTL::RenderCommandEncoder* pEnc = pCmd->renderCommandEncoder( pRpd ); pEnc->setRenderPipelineState( _pPSO ); pEnc->setVertexBuffer( _pVertexPositionsBuffer, 0, 0 ); pEnc->setVertexBuffer( _pVertexColorsBuffer, 0, 1 ); pEnc->drawPrimitives( MTL::PrimitiveType::PrimitiveTypeTriangle, NS::UInteger(0), NS::UInteger(3) ); pEnc->endEncoding(); pCmd->presentDrawable( pView->currentDrawable() ); pCmd->commit(); pPool->release(); } #pragma endregion Renderer }