Implementación inicial de vibe5_metal: sprites con alpha blending
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 <noreply@anthropic.com>
This commit is contained in:
530
source/main.cpp
Normal file
530
source/main.cpp
Normal file
@@ -0,0 +1,530 @@
|
||||
#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 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<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;
|
||||
}
|
||||
|
||||
// Shaders para fondo degradado
|
||||
NSString* backgroundVertexShaderSource = @R"(
|
||||
#include <metal_stdlib>
|
||||
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 <metal_stdlib>
|
||||
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 <metal_stdlib>
|
||||
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 <metal_stdlib>
|
||||
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 <metal_stdlib>
|
||||
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 <metal_stdlib>
|
||||
using namespace metal;
|
||||
|
||||
struct SpriteVertexOut {
|
||||
float4 position [[position]];
|
||||
float2 texCoord;
|
||||
};
|
||||
|
||||
fragment float4 sprite_fragment_main(SpriteVertexOut in [[stage_in]],
|
||||
texture2d<float> spriteTexture [[texture(0)]],
|
||||
sampler textureSampler [[sampler(0)]]) {
|
||||
float4 textureColor = spriteTexture.sample(textureSampler, in.texCoord);
|
||||
return textureColor;
|
||||
}
|
||||
)";
|
||||
|
||||
// Compilar shaders de fondo
|
||||
NSError* error = nil;
|
||||
id<MTLLibrary> 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<MTLLibrary> 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<MTLFunction> backgroundVertexFunction = [backgroundVertexLibrary newFunctionWithName:@"background_vertex_main"];
|
||||
id<MTLFunction> backgroundFragmentFunction = [backgroundFragmentLibrary newFunctionWithName:@"background_fragment_main"];
|
||||
|
||||
// Compilar shaders de triángulo
|
||||
id<MTLLibrary> 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<MTLLibrary> 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<MTLFunction> triangleVertexFunction = [triangleVertexLibrary newFunctionWithName:@"triangle_vertex_main"];
|
||||
id<MTLFunction> triangleFragmentFunction = [triangleFragmentLibrary newFunctionWithName:@"triangle_fragment_main"];
|
||||
|
||||
// Compilar shaders de sprites
|
||||
id<MTLLibrary> 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<MTLLibrary> 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<MTLFunction> spriteVertexFunction = [spriteVertexLibrary newFunctionWithName:@"sprite_vertex_main"];
|
||||
id<MTLFunction> 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<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
|
||||
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<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
|
||||
id<MTLBuffer> 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<CAMetalDrawable> 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<MTLCommandBuffer> commandBuffer = [commandQueue commandBuffer];
|
||||
if (!commandBuffer) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Crear render command encoder
|
||||
id<MTLRenderCommandEncoder> 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
|
||||
}
|
||||
Reference in New Issue
Block a user