d6ffbda00d
- Shaders MSL portados desde GLSL (vertex + fragment) - Estructura básica de MetalShader class - Device, command queue, pipeline y buffers creados - CMakeLists.txt actualizado con Metal frameworks - assets.txt incluye shaders .metal como opcionales PENDIENTE: - Implementar render() loop completo - Obtener MTLTexture desde SDL_Texture - Crear sampler state - Testing en macOS real Ver METAL_BACKEND_NOTES.md para detalles de implementación.
302 lines
9.9 KiB
Plaintext
302 lines
9.9 KiB
Plaintext
// Usar .mm (Objective-C++) en lugar de .cpp para código Metal en macOS
|
|
|
|
#include "metal_shader.h"
|
|
|
|
#ifdef __APPLE__
|
|
|
|
#import <SDL3/SDL.h>
|
|
#import <SDL3/SDL_metal.h>
|
|
#include <vector>
|
|
|
|
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<MTLLibrary> 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<MTLFunction> vertex_function = [library newFunctionWithName:@"vertex_main"];
|
|
id<MTLFunction> 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<MTLTexture> 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__
|