- Crear directorio data/shaders/ para organizar todos los shaders MSL - Extraer shaders embebidos a archivos individuales: * background.metal - Shader de fondo degradado * triangle.metal - Shader de triángulo multicolor * sprite.metal - Shader de sprites con vertex color tinting * crt.metal - Shader CRT post-processing completo - Modificar main.cpp para cargar shaders desde archivos: * Usar stringWithContentsOfFile para leer código fuente * Compilar dinámicamente con newLibraryWithSource * Manejo robusto de errores de lectura y compilación - Eliminar 351 líneas de strings embebidos de main.cpp - Mantener funcionalidad completa: CRT + sprites + fondo + triángulo Beneficios: - Shaders editables sin recompilar ejecutable - Mejor organización y mantenimiento del código - Syntax highlighting completo en editores - Reutilización de shaders en otros proyectos - Desarrollo más ágil de efectos visuales 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
517 lines
23 KiB
C++
517 lines
23 KiB
C++
#include <iostream>
|
||
#include <SDL3/SDL.h>
|
||
|
||
// Headers Metal usando Objective-C++
|
||
#ifdef __APPLE__
|
||
#import <Metal/Metal.h>
|
||
#import <QuartzCore/CAMetalLayer.h>
|
||
#import <Foundation/Foundation.h>
|
||
#import <AppKit/AppKit.h>
|
||
#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 color[4]; // r, g, b, a color components
|
||
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", 960, 720, 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<MTLDevice> 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<MTLCommandQueue> 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;
|
||
}
|
||
|
||
|
||
|
||
|
||
// Cargar shaders CRT desde archivo
|
||
NSError* error = nil;
|
||
|
||
NSString* crtShaderPath = @"data/shaders/crt.metal";
|
||
NSString* crtShaderSource = [NSString stringWithContentsOfFile:crtShaderPath encoding:NSUTF8StringEncoding error:&error];
|
||
if (!crtShaderSource || error) {
|
||
if (error) {
|
||
std::cout << "Failed to read CRT shader file: " << [[error localizedDescription] UTF8String] << std::endl;
|
||
} else {
|
||
std::cout << "Failed to read CRT shader file: data/shaders/crt.metal" << std::endl;
|
||
}
|
||
return -1;
|
||
}
|
||
|
||
id<MTLLibrary> crtLibrary = [device newLibraryWithSource:crtShaderSource options:nil error:&error];
|
||
if (!crtLibrary || error) {
|
||
if (error) {
|
||
std::cout << "Failed to compile CRT shader: " << [[error localizedDescription] UTF8String] << std::endl;
|
||
}
|
||
return -1;
|
||
}
|
||
|
||
id<MTLFunction> crtVertexFunction = [crtLibrary newFunctionWithName:@"crt_vertex_main"];
|
||
id<MTLFunction> crtFragmentFunction = [crtLibrary newFunctionWithName:@"crt_fragment_main"];
|
||
|
||
// Cargar shaders de fondo desde archivo
|
||
NSString* backgroundShaderPath = @"data/shaders/background.metal";
|
||
NSString* backgroundShaderSource = [NSString stringWithContentsOfFile:backgroundShaderPath encoding:NSUTF8StringEncoding error:&error];
|
||
if (!backgroundShaderSource || error) {
|
||
if (error) {
|
||
std::cout << "Failed to read background shader file: " << [[error localizedDescription] UTF8String] << std::endl;
|
||
} else {
|
||
std::cout << "Failed to read background shader file: data/shaders/background.metal" << std::endl;
|
||
}
|
||
return -1;
|
||
}
|
||
|
||
id<MTLLibrary> backgroundLibrary = [device newLibraryWithSource:backgroundShaderSource options:nil error:&error];
|
||
if (!backgroundLibrary || error) {
|
||
if (error) {
|
||
std::cout << "Failed to compile background shader: " << [[error localizedDescription] UTF8String] << std::endl;
|
||
}
|
||
return -1;
|
||
}
|
||
|
||
id<MTLFunction> backgroundVertexFunction = [backgroundLibrary newFunctionWithName:@"background_vertex_main"];
|
||
id<MTLFunction> backgroundFragmentFunction = [backgroundLibrary newFunctionWithName:@"background_fragment_main"];
|
||
|
||
// Cargar shaders de triángulo desde archivo
|
||
NSString* triangleShaderPath = @"data/shaders/triangle.metal";
|
||
NSString* triangleShaderSource = [NSString stringWithContentsOfFile:triangleShaderPath encoding:NSUTF8StringEncoding error:&error];
|
||
if (!triangleShaderSource || error) {
|
||
if (error) {
|
||
std::cout << "Failed to read triangle shader file: " << [[error localizedDescription] UTF8String] << std::endl;
|
||
} else {
|
||
std::cout << "Failed to read triangle shader file: data/shaders/triangle.metal" << std::endl;
|
||
}
|
||
return -1;
|
||
}
|
||
|
||
id<MTLLibrary> triangleLibrary = [device newLibraryWithSource:triangleShaderSource options:nil error:&error];
|
||
if (!triangleLibrary || error) {
|
||
if (error) {
|
||
std::cout << "Failed to compile triangle shader: " << [[error localizedDescription] UTF8String] << std::endl;
|
||
}
|
||
return -1;
|
||
}
|
||
|
||
id<MTLFunction> triangleVertexFunction = [triangleLibrary newFunctionWithName:@"triangle_vertex_main"];
|
||
id<MTLFunction> triangleFragmentFunction = [triangleLibrary newFunctionWithName:@"triangle_fragment_main"];
|
||
|
||
// Cargar shaders de sprites desde archivo
|
||
NSString* spriteShaderPath = @"data/shaders/sprite.metal";
|
||
NSString* spriteShaderSource = [NSString stringWithContentsOfFile:spriteShaderPath encoding:NSUTF8StringEncoding error:&error];
|
||
if (!spriteShaderSource || error) {
|
||
if (error) {
|
||
std::cout << "Failed to read sprite shader file: " << [[error localizedDescription] UTF8String] << std::endl;
|
||
} else {
|
||
std::cout << "Failed to read sprite shader file: data/shaders/sprite.metal" << std::endl;
|
||
}
|
||
return -1;
|
||
}
|
||
|
||
id<MTLLibrary> spriteLibrary = [device newLibraryWithSource:spriteShaderSource options:nil error:&error];
|
||
if (!spriteLibrary || error) {
|
||
if (error) {
|
||
std::cout << "Failed to compile sprite shader: " << [[error localizedDescription] UTF8String] << std::endl;
|
||
}
|
||
return -1;
|
||
}
|
||
|
||
id<MTLFunction> spriteVertexFunction = [spriteLibrary newFunctionWithName:@"sprite_vertex_main"];
|
||
id<MTLFunction> spriteFragmentFunction = [spriteLibrary 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<MTLRenderPipelineState> 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<MTLRenderPipelineState> 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 (position + color + texCoord)
|
||
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 = MTLVertexFormatFloat4; // color
|
||
spriteVertexDescriptor.attributes[1].offset = 8;
|
||
spriteVertexDescriptor.attributes[1].bufferIndex = 0;
|
||
spriteVertexDescriptor.attributes[2].format = MTLVertexFormatFloat2; // texCoord
|
||
spriteVertexDescriptor.attributes[2].offset = 24;
|
||
spriteVertexDescriptor.attributes[2].bufferIndex = 0;
|
||
spriteVertexDescriptor.layouts[0].stride = sizeof(SpriteVertex);
|
||
spriteVertexDescriptor.layouts[0].stepFunction = MTLVertexStepFunctionPerVertex;
|
||
|
||
spritePipelineDescriptor.vertexDescriptor = spriteVertexDescriptor;
|
||
|
||
id<MTLRenderPipelineState> 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<MTLTexture> 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<MTLSamplerState> textureSampler = [device newSamplerStateWithDescriptor:samplerDescriptor];
|
||
if (!textureSampler) {
|
||
std::cout << "Failed to create Metal sampler" << std::endl;
|
||
return -1;
|
||
}
|
||
|
||
// Crear vertex buffer para sprites (para 5 sprites = 30 vértices)
|
||
id<MTLBuffer> spriteVertexBuffer = [device newBufferWithLength:4096 options:MTLResourceCPUCacheModeDefaultCache];
|
||
if (!spriteVertexBuffer) {
|
||
std::cout << "Failed to create sprite vertex buffer" << std::endl;
|
||
return -1;
|
||
}
|
||
|
||
// Crear offscreen render target para CRT post-processing
|
||
CGSize drawableSize = metalLayer.drawableSize;
|
||
MTLTextureDescriptor* offscreenTextureDescriptor = [MTLTextureDescriptor texture2DDescriptorWithPixelFormat:MTLPixelFormatBGRA8Unorm
|
||
width:(NSUInteger)drawableSize.width
|
||
height:(NSUInteger)drawableSize.height
|
||
mipmapped:NO];
|
||
offscreenTextureDescriptor.usage = MTLTextureUsageRenderTarget | MTLTextureUsageShaderRead;
|
||
|
||
id<MTLTexture> offscreenTexture = [device newTextureWithDescriptor:offscreenTextureDescriptor];
|
||
if (!offscreenTexture) {
|
||
std::cout << "Failed to create offscreen render target" << std::endl;
|
||
return -1;
|
||
}
|
||
|
||
// Crear pipeline CRT post-processing
|
||
MTLRenderPipelineDescriptor* crtPipelineDescriptor = [[MTLRenderPipelineDescriptor alloc] init];
|
||
crtPipelineDescriptor.vertexFunction = crtVertexFunction;
|
||
crtPipelineDescriptor.fragmentFunction = crtFragmentFunction;
|
||
crtPipelineDescriptor.colorAttachments[0].pixelFormat = metalLayer.pixelFormat;
|
||
|
||
id<MTLRenderPipelineState> crtPipelineState = [device newRenderPipelineStateWithDescriptor:crtPipelineDescriptor error:&error];
|
||
if (!crtPipelineState || error) {
|
||
if (error) {
|
||
std::cout << "Failed to create CRT render pipeline: " << [[error localizedDescription] UTF8String] << std::endl;
|
||
}
|
||
return -1;
|
||
}
|
||
|
||
// Crear sampler para CRT (linear para suavizar la escalada)
|
||
MTLSamplerDescriptor* crtSamplerDescriptor = [[MTLSamplerDescriptor alloc] init];
|
||
crtSamplerDescriptor.minFilter = MTLSamplerMinMagFilterLinear;
|
||
crtSamplerDescriptor.magFilter = MTLSamplerMinMagFilterLinear;
|
||
crtSamplerDescriptor.sAddressMode = MTLSamplerAddressModeClampToEdge;
|
||
crtSamplerDescriptor.tAddressMode = MTLSamplerAddressModeClampToEdge;
|
||
|
||
id<MTLSamplerState> crtSampler = [device newSamplerStateWithDescriptor:crtSamplerDescriptor];
|
||
if (!crtSampler) {
|
||
std::cout << "Failed to create CRT sampler" << std::endl;
|
||
return -1;
|
||
}
|
||
|
||
std::cout << "Metal pipelines created successfully (background + triangle + sprites + CRT)" << std::endl;
|
||
std::cout << "Sprite texture loaded: " << [bitmap pixelsWide] << "x" << [bitmap pixelsHigh] << std::endl;
|
||
std::cout << "Offscreen render target created: " << (int)drawableSize.width << "x" << (int)drawableSize.height << 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<CAMetalDrawable> drawable = [metalLayer nextDrawable];
|
||
if (!drawable) {
|
||
continue;
|
||
}
|
||
|
||
// PASO 1: Renderizar escena a offscreen texture
|
||
MTLRenderPassDescriptor* offscreenRenderPassDescriptor = [MTLRenderPassDescriptor renderPassDescriptor];
|
||
offscreenRenderPassDescriptor.colorAttachments[0].texture = offscreenTexture;
|
||
offscreenRenderPassDescriptor.colorAttachments[0].loadAction = MTLLoadActionClear;
|
||
offscreenRenderPassDescriptor.colorAttachments[0].clearColor = MTLClearColorMake(0.0, 0.0, 0.0, 1.0);
|
||
offscreenRenderPassDescriptor.colorAttachments[0].storeAction = MTLStoreActionStore;
|
||
|
||
// Crear command buffer
|
||
id<MTLCommandBuffer> commandBuffer = [commandQueue commandBuffer];
|
||
if (!commandBuffer) {
|
||
continue;
|
||
}
|
||
|
||
// Crear render command encoder para offscreen
|
||
id<MTLRenderCommandEncoder> offscreenEncoder = [commandBuffer renderCommandEncoderWithDescriptor:offscreenRenderPassDescriptor];
|
||
if (!offscreenEncoder) {
|
||
continue;
|
||
}
|
||
|
||
// 1. Dibujar fondo degradado primero
|
||
[offscreenEncoder setRenderPipelineState:backgroundPipelineState];
|
||
[offscreenEncoder drawPrimitives:MTLPrimitiveTypeTriangle vertexStart:0 vertexCount:6];
|
||
|
||
// 2. Dibujar triángulo encima
|
||
[offscreenEncoder setRenderPipelineState:trianglePipelineState];
|
||
[offscreenEncoder drawPrimitives:MTLPrimitiveTypeTriangle vertexStart:0 vertexCount:3];
|
||
|
||
// 3. Dibujar 5 sprites con diferentes colores
|
||
[offscreenEncoder setRenderPipelineState:spritePipelineState];
|
||
|
||
// Configurar textura y sampler para sprites
|
||
[offscreenEncoder setFragmentTexture:spriteTexture atIndex:0];
|
||
[offscreenEncoder setFragmentSamplerState:textureSampler atIndex:0];
|
||
|
||
// Crear 5 sprites en diferentes posiciones con diferentes colores
|
||
SpriteVertex spriteVertices[30]; // 5 sprites × 6 vértices cada uno
|
||
|
||
// Configuración común
|
||
float spriteSize = 30.0f;
|
||
float windowWidth = 960.0f;
|
||
float windowHeight = 720.0f;
|
||
float halfWidth = (spriteSize / windowWidth);
|
||
float halfHeight = (spriteSize / windowHeight);
|
||
|
||
// Posiciones de los 5 sprites (evitando el centro donde está el triángulo)
|
||
float positions[5][2] = {
|
||
{-0.6f, 0.6f}, // Esquina superior izquierda
|
||
{ 0.6f, 0.6f}, // Esquina superior derecha
|
||
{-0.6f, -0.6f}, // Esquina inferior izquierda
|
||
{ 0.6f, -0.6f}, // Esquina inferior derecha
|
||
{ 0.0f, -0.8f} // Centro inferior
|
||
};
|
||
|
||
// Colores para cada sprite (RGBA)
|
||
float colors[5][4] = {
|
||
{1.0f, 0.0f, 0.0f, 1.0f}, // Rojo
|
||
{0.0f, 1.0f, 0.0f, 1.0f}, // Verde
|
||
{0.0f, 0.0f, 1.0f, 1.0f}, // Azul
|
||
{1.0f, 1.0f, 0.0f, 1.0f}, // Amarillo
|
||
{1.0f, 0.0f, 1.0f, 1.0f} // Magenta
|
||
};
|
||
|
||
// Generar vértices para los 5 sprites
|
||
for (int i = 0; i < 5; i++) {
|
||
float centerX = positions[i][0];
|
||
float centerY = positions[i][1];
|
||
|
||
// Índice base para este sprite
|
||
int baseIndex = i * 6;
|
||
|
||
// Primer triángulo (bottom-left, bottom-right, top-left)
|
||
spriteVertices[baseIndex + 0] = {{centerX - halfWidth, centerY - halfHeight}, {colors[i][0], colors[i][1], colors[i][2], colors[i][3]}, {0.0f, 1.0f}};
|
||
spriteVertices[baseIndex + 1] = {{centerX + halfWidth, centerY - halfHeight}, {colors[i][0], colors[i][1], colors[i][2], colors[i][3]}, {1.0f, 1.0f}};
|
||
spriteVertices[baseIndex + 2] = {{centerX - halfWidth, centerY + halfHeight}, {colors[i][0], colors[i][1], colors[i][2], colors[i][3]}, {0.0f, 0.0f}};
|
||
|
||
// Segundo triángulo (bottom-right, top-right, top-left)
|
||
spriteVertices[baseIndex + 3] = {{centerX + halfWidth, centerY - halfHeight}, {colors[i][0], colors[i][1], colors[i][2], colors[i][3]}, {1.0f, 1.0f}};
|
||
spriteVertices[baseIndex + 4] = {{centerX + halfWidth, centerY + halfHeight}, {colors[i][0], colors[i][1], colors[i][2], colors[i][3]}, {1.0f, 0.0f}};
|
||
spriteVertices[baseIndex + 5] = {{centerX - halfWidth, centerY + halfHeight}, {colors[i][0], colors[i][1], colors[i][2], colors[i][3]}, {0.0f, 0.0f}};
|
||
}
|
||
|
||
// Copiar vértices al buffer
|
||
void* spriteData = [spriteVertexBuffer contents];
|
||
memcpy(spriteData, spriteVertices, sizeof(spriteVertices));
|
||
|
||
// Configurar vertex buffer y dibujar todos los sprites en offscreen
|
||
[offscreenEncoder setVertexBuffer:spriteVertexBuffer offset:0 atIndex:0];
|
||
[offscreenEncoder drawPrimitives:MTLPrimitiveTypeTriangle vertexStart:0 vertexCount:30];
|
||
|
||
[offscreenEncoder endEncoding];
|
||
|
||
// PASO 2: Aplicar CRT post-processing a la pantalla final
|
||
MTLRenderPassDescriptor* finalRenderPassDescriptor = [MTLRenderPassDescriptor renderPassDescriptor];
|
||
finalRenderPassDescriptor.colorAttachments[0].texture = drawable.texture;
|
||
finalRenderPassDescriptor.colorAttachments[0].loadAction = MTLLoadActionClear;
|
||
finalRenderPassDescriptor.colorAttachments[0].clearColor = MTLClearColorMake(0.0, 0.0, 0.0, 1.0);
|
||
finalRenderPassDescriptor.colorAttachments[0].storeAction = MTLStoreActionStore;
|
||
|
||
// Crear encoder para CRT post-processing
|
||
id<MTLRenderCommandEncoder> crtEncoder = [commandBuffer renderCommandEncoderWithDescriptor:finalRenderPassDescriptor];
|
||
if (!crtEncoder) {
|
||
continue;
|
||
}
|
||
|
||
// Aplicar CRT shader al resultado offscreen
|
||
[crtEncoder setRenderPipelineState:crtPipelineState];
|
||
[crtEncoder setFragmentTexture:offscreenTexture atIndex:0];
|
||
[crtEncoder setFragmentSamplerState:crtSampler atIndex:0];
|
||
[crtEncoder drawPrimitives:MTLPrimitiveTypeTriangle vertexStart:0 vertexCount:6];
|
||
|
||
[crtEncoder 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
|
||
} |