// Usar .mm (Objective-C++) en lugar de .cpp para código Metal en macOS #include "metal_shader.h" #ifdef __APPLE__ #import #import #include namespace Rendering { MetalShader::~MetalShader() { cleanup(); } bool MetalShader::createMetalDevice() { // Obtener el device Metal por defecto (GPU del sistema) device_ = MTLCreateSystemDefaultDevice(); if (!device_) { SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "ERROR: No se pudo crear Metal device"); return false; } // Crear command queue command_queue_ = [device_ newCommandQueue]; if (!command_queue_) { SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "ERROR: No se pudo crear Metal command queue"); return false; } SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "Metal device creado: %s", [[device_ name] UTF8String]); return true; } bool MetalShader::compileShaders(const std::string& vertex_source, const std::string& fragment_source) { // En Metal, los shaders se pueden compilar de dos formas: // 1. Runtime compilation desde strings (lo que haremos aquí) // 2. Pre-compilados a .metallib (más eficiente pero requiere build step) NSError* error = nil; // Convertir sources a NSString NSString* vertex_code = [NSString stringWithUTF8String:vertex_source.c_str()]; NSString* fragment_code = [NSString stringWithUTF8String:fragment_source.c_str()]; // Combinar vertex + fragment en un solo source (Metal permite múltiples shaders por library) NSString* combined_source = [NSString stringWithFormat:@"%@\n\n%@", vertex_code, fragment_code]; // Crear library desde source code id library = [device_ newLibraryWithSource:combined_source options:nil error:&error]; if (!library) { SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "ERROR: Fallo al compilar shaders Metal: %s", [[error localizedDescription] UTF8String]); return false; } // Obtener funciones del library id vertex_function = [library newFunctionWithName:@"vertex_main"]; id fragment_function = [library newFunctionWithName:@"fragment_main"]; if (!vertex_function || !fragment_function) { SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "ERROR: No se encontraron las funciones vertex_main o fragment_main"); return false; } // Crear pipeline descriptor MTLRenderPipelineDescriptor* pipeline_descriptor = [[MTLRenderPipelineDescriptor alloc] init]; pipeline_descriptor.vertexFunction = vertex_function; pipeline_descriptor.fragmentFunction = fragment_function; // Configurar formato de color (BGRA8Unorm es común en macOS) pipeline_descriptor.colorAttachments[0].pixelFormat = MTLPixelFormatBGRA8Unorm; // Configurar vertex descriptor (cómo están organizados los vértices) MTLVertexDescriptor* vertex_descriptor = [[MTLVertexDescriptor alloc] init]; // Atributo 0: position (2 floats) vertex_descriptor.attributes[0].format = MTLVertexFormatFloat2; vertex_descriptor.attributes[0].offset = 0; vertex_descriptor.attributes[0].bufferIndex = 0; // Atributo 1: texCoord (2 floats) vertex_descriptor.attributes[1].format = MTLVertexFormatFloat2; vertex_descriptor.attributes[1].offset = 2 * sizeof(float); vertex_descriptor.attributes[1].bufferIndex = 0; // Layout del buffer (stride = 4 floats por vértice) vertex_descriptor.layouts[0].stride = 4 * sizeof(float); vertex_descriptor.layouts[0].stepRate = 1; vertex_descriptor.layouts[0].stepFunction = MTLVertexStepFunctionPerVertex; pipeline_descriptor.vertexDescriptor = vertex_descriptor; // Crear pipeline state pipeline_state_ = [device_ newRenderPipelineStateWithDescriptor:pipeline_descriptor error:&error]; if (!pipeline_state_) { SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "ERROR: Fallo al crear pipeline state: %s", [[error localizedDescription] UTF8String]); return false; } SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "Shaders Metal compilados correctamente"); return true; } bool MetalShader::createBuffers() { // Crear quad de pantalla completa (igual que en OpenGL) // Formato: x, y, u, v float vertices[] = { -1.0f, -1.0f, 0.0f, 0.0f, // Inferior izquierda 1.0f, -1.0f, 1.0f, 0.0f, // Inferior derecha 1.0f, 1.0f, 1.0f, 1.0f, // Superior derecha -1.0f, 1.0f, 0.0f, 1.0f // Superior izquierda }; uint16_t indices[] = { 0, 1, 2, // Primer triángulo 2, 3, 0 // Segundo triángulo }; // Crear vertex buffer vertex_buffer_ = [device_ newBufferWithBytes:vertices length:sizeof(vertices) options:MTLResourceStorageModeShared]; if (!vertex_buffer_) { SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "ERROR: Fallo al crear vertex buffer"); return false; } // Crear index buffer index_buffer_ = [device_ newBufferWithBytes:indices length:sizeof(indices) options:MTLResourceStorageModeShared]; if (!index_buffer_) { SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "ERROR: Fallo al crear index buffer"); return false; } // Crear uniforms buffer uniforms_buffer_ = [device_ newBufferWithLength:sizeof(Uniforms) options:MTLResourceStorageModeShared]; if (!uniforms_buffer_) { SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "ERROR: Fallo al crear uniforms buffer"); return false; } return true; } bool MetalShader::init(SDL_Window* window, SDL_Texture* texture, const std::string& vertex_source, const std::string& fragment_source) { window_ = window; back_buffer_ = texture; renderer_ = SDL_GetRenderer(window); if (!renderer_) { SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "ERROR: No se pudo obtener el renderer"); return false; } // Verificar que es Metal renderer const char* renderer_name = SDL_GetRendererName(renderer_); if (!renderer_name || strncmp(renderer_name, "metal", 5) != 0) { SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION, "Renderer no es Metal: %s", renderer_name ? renderer_name : "unknown"); return false; } // Obtener tamaños SDL_GetWindowSize(window_, &window_width_, &window_height_); SDL_GetTextureSize(back_buffer_, &texture_width_, &texture_height_); // Crear Metal device if (!createMetalDevice()) { return false; } // Compilar shaders if (!compileShaders(vertex_source, fragment_source)) { return false; } // Crear buffers if (!createBuffers()) { return false; } // Inicializar uniforms uniforms_.textureSize[0] = texture_width_; uniforms_.textureSize[1] = texture_height_; // Copiar uniforms al buffer memcpy([uniforms_buffer_ contents], &uniforms_, sizeof(Uniforms)); // Obtener Metal layer de SDL metal_layer_ = (__bridge CAMetalLayer*)SDL_GetProperty( SDL_GetWindowProperties(window_), SDL_PROP_WINDOW_METAL_LAYER_POINTER, nullptr ); if (!metal_layer_) { SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "ERROR: No se pudo obtener Metal layer"); return false; } metal_layer_.device = device_; metal_layer_.pixelFormat = MTLPixelFormatBGRA8Unorm; is_initialized_ = true; SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "** Metal Shader Backend inicializado correctamente"); return true; } void MetalShader::render() { if (!is_initialized_ || !pipeline_state_) { // Fallback a renderizado normal SDL_SetRenderDrawColor(renderer_, 0, 0, 0, 255); SDL_SetRenderTarget(renderer_, nullptr); SDL_RenderClear(renderer_); SDL_RenderTexture(renderer_, back_buffer_, nullptr, nullptr); SDL_RenderPresent(renderer_); return; } // TODO: Implementar render loop de Metal // 1. Obtener drawable del layer // 2. Crear command buffer // 3. Crear render pass descriptor // 4. Encodear comandos de dibujo // 5. Commit y presentar // NOTA: Esta es la parte más compleja y requiere más trabajo // Por ahora dejamos un stub para que compile SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION, "Metal render() no implementado completamente todavía"); } void MetalShader::setTextureSize(float width, float height) { if (!is_initialized_) { return; } texture_width_ = width; texture_height_ = height; // Actualizar uniforms uniforms_.textureSize[0] = width; uniforms_.textureSize[1] = height; memcpy([uniforms_buffer_ contents], &uniforms_, sizeof(Uniforms)); } void MetalShader::cleanup() { // En Objective-C++ con ARC, no necesitamos release manual // pero limpiamos las referencias pipeline_state_ = nil; command_queue_ = nil; vertex_buffer_ = nil; index_buffer_ = nil; uniforms_buffer_ = nil; device_ = nil; metal_layer_ = nil; is_initialized_ = false; } id MetalShader::getMetalTexture(SDL_Texture* texture) { // TODO: Obtener la textura Metal desde SDL_Texture // Esto requiere acceder a las properties de SDL3 return nil; } } // namespace Rendering #endif // __APPLE__