From eed0eeb7475b4e2ee92727e7a70c2a75932d284e Mon Sep 17 00:00:00 2001 From: Sergio Valor Date: Sun, 28 Sep 2025 18:07:52 +0200 Subject: [PATCH] =?UTF-8?q?Implementaci=C3=B3n=20inicial=20de=20vibe5=5Fme?= =?UTF-8?q?tal:=20sprites=20con=20alpha=20blending?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Motor de renderizado Metal completo con: - Renderizado de fondo degradado (púrpura a cyan) - Triángulo RGB con interpolación de colores - Renderizado de sprites con alpha blending y filtro NEAREST - Integración de Metal Performance HUD para métricas FPS/debug - Integración SDL3 + Metal usando Objective-C++ Características implementadas: ✅ Múltiples pipelines de renderizado (fondo, triángulo, sprites) ✅ Carga de texturas con NSImage/CoreGraphics ✅ Configuración de alpha blending para sprites ✅ Muestreo de texturas anti-blur (MTLSamplerMinMagFilterNearest) ✅ Estructuras de vértices y shaders apropiados ✅ Metal HUD para monitoreo de rendimiento 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- .gitignore | 15 ++ CMakeLists.txt | 41 ++++ data/ball.png | Bin 0 -> 162 bytes source/main.cpp | 530 ++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 586 insertions(+) create mode 100644 .gitignore create mode 100644 CMakeLists.txt create mode 100644 data/ball.png create mode 100644 source/main.cpp diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..38f92dd --- /dev/null +++ b/.gitignore @@ -0,0 +1,15 @@ +# Build artifacts +build/ +vibe5_metal + +# macOS +.DS_Store +.vscode/ + +# IDE +*.swp +*.swo +*~ + +# Logs +*.log \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..c09792f --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,41 @@ +cmake_minimum_required(VERSION 3.10) +project(vibe5_metal) + +# C++ estándar +set(CMAKE_CXX_STANDARD 20) +set(CMAKE_CXX_STANDARD_REQUIRED True) + +# Buscar SDL3 +find_package(SDL3 REQUIRED CONFIG REQUIRED COMPONENTS SDL3) + +# Crear ejecutable principal +add_executable(vibe5_metal source/main.cpp) + +# Solo en macOS, usar Objective-C++ +if(APPLE) + set_source_files_properties(source/main.cpp PROPERTIES COMPILE_FLAGS "-x objective-c++") + + # Frameworks necesarios en macOS + find_library(METAL_FRAMEWORK Metal) + find_library(QUARTZCORE_FRAMEWORK QuartzCore) + find_library(FOUNDATION_FRAMEWORK Foundation) + find_library(APPKIT_FRAMEWORK AppKit) + + target_link_libraries(vibe5_metal + SDL3::SDL3 + ${METAL_FRAMEWORK} + ${QUARTZCORE_FRAMEWORK} + ${FOUNDATION_FRAMEWORK} + ${APPKIT_FRAMEWORK} + ) +else() + target_link_libraries(vibe5_metal SDL3::SDL3) +endif() + +# Definir macro para macOS +if(APPLE) + target_compile_definitions(vibe5_metal PRIVATE __APPLE__) +endif() + +# Ubicación del ejecutable +set_target_properties(vibe5_metal PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${CMAKE_SOURCE_DIR}) \ No newline at end of file diff --git a/data/ball.png b/data/ball.png new file mode 100644 index 0000000000000000000000000000000000000000..4ead2915859c5d701f404437e6517d0b0c921680 GIT binary patch literal 162 zcmeAS@N?(olHy`uVBq!ia0vp^AT}2V8<6ZZI=>f4F%}28J29*~C-V}>iScxC43U`H zJK-Yd0R)={vUVaC*|j~Wx_%-s2O z@{&(`ckZ>~*!$ho+<3F$vFZDd-e-uY%{u=0w5MI4S6u4ZUk}AidpZp~T*IFO?P2hA L^>bP0l+XkKX~I1; literal 0 HcmV?d00001 diff --git a/source/main.cpp b/source/main.cpp new file mode 100644 index 0000000..92a94ed --- /dev/null +++ b/source/main.cpp @@ -0,0 +1,530 @@ +#include +#include + +// Headers Metal usando Objective-C++ +#ifdef __APPLE__ +#import +#import +#import +#import +#endif + +/* + * METAL PERFORMANCE HUD - Debug FPS y métricas en tiempo real + * + * Para mostrar FPS y estadísticas de rendimiento, ejecutar con: + * + * MTL_HUD_ENABLED=1 ./vibe5_metal + * + * El HUD aparece en la esquina superior derecha y muestra: + * - FPS (Frames Per Second) + * - CPU y GPU usage + * - Memory usage + * - Device info y resolución + * + * Atajos de teclado disponibles cuando el HUD está activo: + * - Command+F9: Enable/Disable HUD + * - Command+F8: Enable/Disable Logging + * - Command+F11: Reset Metrics + * - Command+F12: Show Configuration Panel + * + * Para activar globalmente para todas las apps Metal: + * launchctl setenv MTL_HUD_ENABLED 1 + * + * Para desactivar: + * launchctl setenv MTL_HUD_ENABLED 0 + */ + +// Estructura para vértices de sprites +struct SpriteVertex { + float position[2]; // x, y coordinates + float texCoord[2]; // u, v texture coordinates +}; + +int main(int argc, char* argv[]) { +#ifdef __APPLE__ + // Configurar SDL para usar Metal + SDL_SetHint(SDL_HINT_RENDER_DRIVER, "metal"); + + // Inicializar SDL + if (!SDL_Init(SDL_INIT_VIDEO)) { + std::cout << "SDL_Init failed: " << SDL_GetError() << std::endl; + return -1; + } + + // Crear ventana + SDL_Window* window = SDL_CreateWindow("vibe5_metal - Triangle + Gradient + Sprites", 640, 480, SDL_WINDOW_HIGH_PIXEL_DENSITY); + if (!window) { + std::cout << "SDL_CreateWindow failed: " << SDL_GetError() << std::endl; + SDL_Quit(); + return -1; + } + + // Crear renderer + SDL_Renderer* renderer = SDL_CreateRenderer(window, nullptr); + if (!renderer) { + std::cout << "SDL_CreateRenderer failed: " << SDL_GetError() << std::endl; + SDL_DestroyWindow(window); + SDL_Quit(); + return -1; + } + + std::cout << "SDL Window and Renderer created successfully" << std::endl; + + // Obtener Metal layer + CAMetalLayer* metalLayer = (__bridge CAMetalLayer*)SDL_GetRenderMetalLayer(renderer); + if (!metalLayer) { + std::cout << "Failed to get Metal layer - SDL may not be using Metal driver" << std::endl; + SDL_DestroyRenderer(renderer); + SDL_DestroyWindow(window); + SDL_Quit(); + return -1; + } + + // Obtener device Metal del layer + id device = metalLayer.device; + if (!device) { + std::cout << "Failed to get Metal device from layer" << std::endl; + SDL_DestroyRenderer(renderer); + SDL_DestroyWindow(window); + SDL_Quit(); + return -1; + } + + std::cout << "Got Metal device: " << [[device name] UTF8String] << std::endl; + + // Crear command queue + id commandQueue = [device newCommandQueue]; + if (!commandQueue) { + std::cout << "Failed to create Metal command queue" << std::endl; + SDL_DestroyRenderer(renderer); + SDL_DestroyWindow(window); + SDL_Quit(); + return -1; + } + + // Shaders para fondo degradado + NSString* backgroundVertexShaderSource = @R"( +#include +using namespace metal; + +struct VertexOut { + float4 position [[position]]; + float4 color; +}; + +vertex VertexOut background_vertex_main(uint vertexID [[vertex_id]]) { + VertexOut out; + + // Quad de pantalla completa en coordenadas NDC + float2 positions[6] = { + float2(-1.0, -1.0), // Bottom left + float2( 1.0, -1.0), // Bottom right + float2(-1.0, 1.0), // Top left + float2( 1.0, -1.0), // Bottom right + float2( 1.0, 1.0), // Top right + float2(-1.0, 1.0) // Top left + }; + + // Gradiente de púrpura oscuro arriba a azul cyan abajo + float4 colors[6] = { + float4(0.2, 0.6, 0.8, 1.0), // Bottom left - cyan claro + float4(0.2, 0.6, 0.8, 1.0), // Bottom right - cyan claro + float4(0.3, 0.1, 0.5, 1.0), // Top left - púrpura oscuro + float4(0.2, 0.6, 0.8, 1.0), // Bottom right - cyan claro + float4(0.3, 0.1, 0.5, 1.0), // Top right - púrpura oscuro + float4(0.3, 0.1, 0.5, 1.0) // Top left - púrpura oscuro + }; + + out.position = float4(positions[vertexID], 0.0, 1.0); + out.color = colors[vertexID]; + + return out; +} +)"; + + NSString* backgroundFragmentShaderSource = @R"( +#include +using namespace metal; + +struct VertexOut { + float4 position [[position]]; + float4 color; +}; + +fragment float4 background_fragment_main(VertexOut in [[stage_in]]) { + return in.color; +} +)"; + + // Shaders para triángulo + NSString* triangleVertexShaderSource = @R"( +#include +using namespace metal; + +struct VertexOut { + float4 position [[position]]; + float4 color; +}; + +vertex VertexOut triangle_vertex_main(uint vertexID [[vertex_id]]) { + VertexOut out; + + // Triángulo simple en coordenadas normalized device coordinates + float2 positions[3] = { + float2( 0.0, 0.5), // Top + float2(-0.5, -0.5), // Bottom left + float2( 0.5, -0.5) // Bottom right + }; + + float4 colors[3] = { + float4(1, 0, 0, 1), // Red + float4(0, 1, 0, 1), // Green + float4(0, 0, 1, 1) // Blue + }; + + out.position = float4(positions[vertexID], 0.0, 1.0); + out.color = colors[vertexID]; + + return out; +} +)"; + + NSString* triangleFragmentShaderSource = @R"( +#include +using namespace metal; + +struct VertexOut { + float4 position [[position]]; + float4 color; +}; + +fragment float4 triangle_fragment_main(VertexOut in [[stage_in]]) { + return in.color; +} +)"; + + // Shaders para sprites con textura + NSString* spriteVertexShaderSource = @R"( +#include +using namespace metal; + +struct SpriteVertexIn { + float2 position [[attribute(0)]]; + float2 texCoord [[attribute(1)]]; +}; + +struct SpriteVertexOut { + float4 position [[position]]; + float2 texCoord; +}; + +vertex SpriteVertexOut sprite_vertex_main(SpriteVertexIn in [[stage_in]]) { + SpriteVertexOut out; + out.position = float4(in.position, 0.0, 1.0); + out.texCoord = in.texCoord; + return out; +} +)"; + + NSString* spriteFragmentShaderSource = @R"( +#include +using namespace metal; + +struct SpriteVertexOut { + float4 position [[position]]; + float2 texCoord; +}; + +fragment float4 sprite_fragment_main(SpriteVertexOut in [[stage_in]], + texture2d spriteTexture [[texture(0)]], + sampler textureSampler [[sampler(0)]]) { + float4 textureColor = spriteTexture.sample(textureSampler, in.texCoord); + return textureColor; +} +)"; + + // Compilar shaders de fondo + NSError* error = nil; + id backgroundVertexLibrary = [device newLibraryWithSource:backgroundVertexShaderSource options:nil error:&error]; + if (!backgroundVertexLibrary || error) { + if (error) { + std::cout << "Failed to compile background vertex shader: " << [[error localizedDescription] UTF8String] << std::endl; + } + return -1; + } + + id backgroundFragmentLibrary = [device newLibraryWithSource:backgroundFragmentShaderSource options:nil error:&error]; + if (!backgroundFragmentLibrary || error) { + if (error) { + std::cout << "Failed to compile background fragment shader: " << [[error localizedDescription] UTF8String] << std::endl; + } + return -1; + } + + id backgroundVertexFunction = [backgroundVertexLibrary newFunctionWithName:@"background_vertex_main"]; + id backgroundFragmentFunction = [backgroundFragmentLibrary newFunctionWithName:@"background_fragment_main"]; + + // Compilar shaders de triángulo + id triangleVertexLibrary = [device newLibraryWithSource:triangleVertexShaderSource options:nil error:&error]; + if (!triangleVertexLibrary || error) { + if (error) { + std::cout << "Failed to compile triangle vertex shader: " << [[error localizedDescription] UTF8String] << std::endl; + } + return -1; + } + + id triangleFragmentLibrary = [device newLibraryWithSource:triangleFragmentShaderSource options:nil error:&error]; + if (!triangleFragmentLibrary || error) { + if (error) { + std::cout << "Failed to compile triangle fragment shader: " << [[error localizedDescription] UTF8String] << std::endl; + } + return -1; + } + + id triangleVertexFunction = [triangleVertexLibrary newFunctionWithName:@"triangle_vertex_main"]; + id triangleFragmentFunction = [triangleFragmentLibrary newFunctionWithName:@"triangle_fragment_main"]; + + // Compilar shaders de sprites + id spriteVertexLibrary = [device newLibraryWithSource:spriteVertexShaderSource options:nil error:&error]; + if (!spriteVertexLibrary || error) { + if (error) { + std::cout << "Failed to compile sprite vertex shader: " << [[error localizedDescription] UTF8String] << std::endl; + } + return -1; + } + + id spriteFragmentLibrary = [device newLibraryWithSource:spriteFragmentShaderSource options:nil error:&error]; + if (!spriteFragmentLibrary || error) { + if (error) { + std::cout << "Failed to compile sprite fragment shader: " << [[error localizedDescription] UTF8String] << std::endl; + } + return -1; + } + + id spriteVertexFunction = [spriteVertexLibrary newFunctionWithName:@"sprite_vertex_main"]; + id spriteFragmentFunction = [spriteFragmentLibrary newFunctionWithName:@"sprite_fragment_main"]; + + // Crear pipeline de fondo + MTLRenderPipelineDescriptor* backgroundPipelineDescriptor = [[MTLRenderPipelineDescriptor alloc] init]; + backgroundPipelineDescriptor.vertexFunction = backgroundVertexFunction; + backgroundPipelineDescriptor.fragmentFunction = backgroundFragmentFunction; + backgroundPipelineDescriptor.colorAttachments[0].pixelFormat = metalLayer.pixelFormat; + + id backgroundPipelineState = [device newRenderPipelineStateWithDescriptor:backgroundPipelineDescriptor error:&error]; + if (!backgroundPipelineState || error) { + if (error) { + std::cout << "Failed to create background render pipeline: " << [[error localizedDescription] UTF8String] << std::endl; + } + return -1; + } + + // Crear pipeline de triángulo + MTLRenderPipelineDescriptor* trianglePipelineDescriptor = [[MTLRenderPipelineDescriptor alloc] init]; + trianglePipelineDescriptor.vertexFunction = triangleVertexFunction; + trianglePipelineDescriptor.fragmentFunction = triangleFragmentFunction; + trianglePipelineDescriptor.colorAttachments[0].pixelFormat = metalLayer.pixelFormat; + + id trianglePipelineState = [device newRenderPipelineStateWithDescriptor:trianglePipelineDescriptor error:&error]; + if (!trianglePipelineState || error) { + if (error) { + std::cout << "Failed to create triangle render pipeline: " << [[error localizedDescription] UTF8String] << std::endl; + } + return -1; + } + + // Crear pipeline de sprites con alpha blending + MTLRenderPipelineDescriptor* spritePipelineDescriptor = [[MTLRenderPipelineDescriptor alloc] init]; + spritePipelineDescriptor.vertexFunction = spriteVertexFunction; + spritePipelineDescriptor.fragmentFunction = spriteFragmentFunction; + spritePipelineDescriptor.colorAttachments[0].pixelFormat = metalLayer.pixelFormat; + + // Configurar alpha blending + spritePipelineDescriptor.colorAttachments[0].blendingEnabled = YES; + spritePipelineDescriptor.colorAttachments[0].rgbBlendOperation = MTLBlendOperationAdd; + spritePipelineDescriptor.colorAttachments[0].alphaBlendOperation = MTLBlendOperationAdd; + spritePipelineDescriptor.colorAttachments[0].sourceRGBBlendFactor = MTLBlendFactorSourceAlpha; + spritePipelineDescriptor.colorAttachments[0].sourceAlphaBlendFactor = MTLBlendFactorSourceAlpha; + spritePipelineDescriptor.colorAttachments[0].destinationRGBBlendFactor = MTLBlendFactorOneMinusSourceAlpha; + spritePipelineDescriptor.colorAttachments[0].destinationAlphaBlendFactor = MTLBlendFactorOneMinusSourceAlpha; + + // Configurar vertex descriptor para sprites + MTLVertexDescriptor* spriteVertexDescriptor = [[MTLVertexDescriptor alloc] init]; + spriteVertexDescriptor.attributes[0].format = MTLVertexFormatFloat2; // position + spriteVertexDescriptor.attributes[0].offset = 0; + spriteVertexDescriptor.attributes[0].bufferIndex = 0; + spriteVertexDescriptor.attributes[1].format = MTLVertexFormatFloat2; // texCoord + spriteVertexDescriptor.attributes[1].offset = 8; + spriteVertexDescriptor.attributes[1].bufferIndex = 0; + spriteVertexDescriptor.layouts[0].stride = sizeof(SpriteVertex); + spriteVertexDescriptor.layouts[0].stepFunction = MTLVertexStepFunctionPerVertex; + + spritePipelineDescriptor.vertexDescriptor = spriteVertexDescriptor; + + id spritePipelineState = [device newRenderPipelineStateWithDescriptor:spritePipelineDescriptor error:&error]; + if (!spritePipelineState || error) { + if (error) { + std::cout << "Failed to create sprite render pipeline: " << [[error localizedDescription] UTF8String] << std::endl; + } + return -1; + } + + // Cargar textura de sprite + NSString* texturePath = @"data/ball.png"; + NSImage* image = [[NSImage alloc] initWithContentsOfFile:texturePath]; + if (!image) { + std::cout << "Failed to load texture image: data/ball.png" << std::endl; + return -1; + } + + // Obtener representación como bitmap + NSBitmapImageRep* bitmap = [[NSBitmapImageRep alloc] initWithData:[image TIFFRepresentation]]; + if (!bitmap) { + std::cout << "Failed to create bitmap from image" << std::endl; + return -1; + } + + // Crear descriptor de textura Metal + MTLTextureDescriptor* textureDescriptor = [MTLTextureDescriptor texture2DDescriptorWithPixelFormat:MTLPixelFormatRGBA8Unorm + width:[bitmap pixelsWide] + height:[bitmap pixelsHigh] + mipmapped:NO]; + + // Crear textura Metal + id spriteTexture = [device newTextureWithDescriptor:textureDescriptor]; + if (!spriteTexture) { + std::cout << "Failed to create Metal texture" << std::endl; + return -1; + } + + // Copiar datos de imagen a textura Metal + MTLRegion region = MTLRegionMake2D(0, 0, [bitmap pixelsWide], [bitmap pixelsHigh]); + [spriteTexture replaceRegion:region + mipmapLevel:0 + withBytes:[bitmap bitmapData] + bytesPerRow:[bitmap bytesPerRow]]; + + // Crear sampler con filtro NEAREST para evitar blur + MTLSamplerDescriptor* samplerDescriptor = [[MTLSamplerDescriptor alloc] init]; + samplerDescriptor.minFilter = MTLSamplerMinMagFilterNearest; + samplerDescriptor.magFilter = MTLSamplerMinMagFilterNearest; + samplerDescriptor.sAddressMode = MTLSamplerAddressModeClampToEdge; + samplerDescriptor.tAddressMode = MTLSamplerAddressModeClampToEdge; + + id textureSampler = [device newSamplerStateWithDescriptor:samplerDescriptor]; + if (!textureSampler) { + std::cout << "Failed to create Metal sampler" << std::endl; + return -1; + } + + // Crear vertex buffer para sprites + id spriteVertexBuffer = [device newBufferWithLength:1024 options:MTLResourceCPUCacheModeDefaultCache]; + if (!spriteVertexBuffer) { + std::cout << "Failed to create sprite vertex buffer" << std::endl; + return -1; + } + + std::cout << "Metal pipelines created successfully (background + triangle + sprites)" << std::endl; + std::cout << "Sprite texture loaded: " << [bitmap pixelsWide] << "x" << [bitmap pixelsHigh] << std::endl; + + // Main loop + bool quit = false; + SDL_Event e; + + while (!quit) { + while (SDL_PollEvent(&e)) { + if (e.type == SDL_EVENT_QUIT) { + quit = true; + } + } + + // Obtener drawable del Metal layer + id drawable = [metalLayer nextDrawable]; + if (!drawable) { + continue; + } + + // Crear render pass descriptor + MTLRenderPassDescriptor* renderPassDescriptor = [MTLRenderPassDescriptor renderPassDescriptor]; + renderPassDescriptor.colorAttachments[0].texture = drawable.texture; + renderPassDescriptor.colorAttachments[0].loadAction = MTLLoadActionClear; + renderPassDescriptor.colorAttachments[0].clearColor = MTLClearColorMake(0.0, 0.0, 0.0, 1.0); + renderPassDescriptor.colorAttachments[0].storeAction = MTLStoreActionStore; + + // Crear command buffer + id commandBuffer = [commandQueue commandBuffer]; + if (!commandBuffer) { + continue; + } + + // Crear render command encoder + id renderEncoder = [commandBuffer renderCommandEncoderWithDescriptor:renderPassDescriptor]; + if (!renderEncoder) { + continue; + } + + // 1. Dibujar fondo degradado primero + [renderEncoder setRenderPipelineState:backgroundPipelineState]; + [renderEncoder drawPrimitives:MTLPrimitiveTypeTriangle vertexStart:0 vertexCount:6]; + + // 2. Dibujar triángulo encima + [renderEncoder setRenderPipelineState:trianglePipelineState]; + [renderEncoder drawPrimitives:MTLPrimitiveTypeTriangle vertexStart:0 vertexCount:3]; + + // 3. Dibujar sprite con alpha blending + [renderEncoder setRenderPipelineState:spritePipelineState]; + + // Configurar textura y sampler + [renderEncoder setFragmentTexture:spriteTexture atIndex:0]; + [renderEncoder setFragmentSamplerState:textureSampler atIndex:0]; + + // Crear sprite centrado con un tamaño pequeño + SpriteVertex spriteVertices[6]; // 2 triángulos para formar un quad + + // Sprite centrado de 64x64 pixels en pantalla de 640x480 + float spriteSize = 64.0f; + float windowWidth = 640.0f; + float windowHeight = 480.0f; + + // Convertir a coordenadas NDC + float halfWidth = (spriteSize / windowWidth); // 0.1 en NDC + float halfHeight = (spriteSize / windowHeight); // 0.133 en NDC + + // Primer triángulo (bottom-left, bottom-right, top-left) + spriteVertices[0] = {{-halfWidth, -halfHeight}, {0.0f, 1.0f}}; // bottom-left + spriteVertices[1] = {{ halfWidth, -halfHeight}, {1.0f, 1.0f}}; // bottom-right + spriteVertices[2] = {{-halfWidth, halfHeight}, {0.0f, 0.0f}}; // top-left + + // Segundo triángulo (bottom-right, top-right, top-left) + spriteVertices[3] = {{ halfWidth, -halfHeight}, {1.0f, 1.0f}}; // bottom-right + spriteVertices[4] = {{ halfWidth, halfHeight}, {1.0f, 0.0f}}; // top-right + spriteVertices[5] = {{-halfWidth, halfHeight}, {0.0f, 0.0f}}; // top-left + + // Copiar vértices al buffer + void* spriteData = [spriteVertexBuffer contents]; + memcpy(spriteData, spriteVertices, sizeof(spriteVertices)); + + // Configurar vertex buffer y dibujar + [renderEncoder setVertexBuffer:spriteVertexBuffer offset:0 atIndex:0]; + [renderEncoder drawPrimitives:MTLPrimitiveTypeTriangle vertexStart:0 vertexCount:6]; + + [renderEncoder endEncoding]; + + // Presentar drawable + [commandBuffer presentDrawable:drawable]; + [commandBuffer commit]; + } + + // Cleanup + SDL_DestroyRenderer(renderer); + SDL_DestroyWindow(window); + SDL_Quit(); + + std::cout << "vibe5_metal finished successfully" << std::endl; + return 0; + +#else + std::cout << "This example requires macOS and Metal support" << std::endl; + return -1; +#endif +} \ No newline at end of file