- 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.
5.7 KiB
Metal Shader Backend - Notas de Implementación
Estado Actual
✅ Completado:
- Shaders MSL (Metal Shading Language) portados desde GLSL
- Estructura básica de
MetalShaderclass - Inicialización de Metal device y command queue
- Compilación de shaders en runtime
- Creación de pipeline state
- Buffers de vértices, índices y uniforms
❌ Pendiente:
- Render loop completo (la parte más crítica)
- Obtener textura Metal desde SDL_Texture
- Gestión de drawables y presentation
Diferencias GLSL vs MSL
| Concepto | GLSL (OpenGL) | MSL (Metal) |
|---|---|---|
| Entrada vertex | layout(location = 0) in vec2 |
[[attribute(0)]] |
| Salida vertex | out vec2 |
Struct con [[position]] |
| Uniforms | uniform vec2 |
constant struct en [[buffer(N)]] |
| Sampling | texture(sampler2D, vec2) |
texture.sample(sampler, float2) |
| Entry point | void main() |
vertex/fragment function_name() |
| Vector types | vec2, vec3, vec4 |
float2, float3, float4 |
Pasos para Completar el Render Loop
El método MetalShader::render() necesita:
1. Obtener drawable del CAMetalLayer:
id<CAMetalDrawable> drawable = [metal_layer_ nextDrawable];
2. Crear command buffer:
id<MTLCommandBuffer> command_buffer = [command_queue_ commandBuffer];
3. Crear render pass descriptor:
MTLRenderPassDescriptor* pass_descriptor = [MTLRenderPassDescriptor renderPassDescriptor];
pass_descriptor.colorAttachments[0].texture = drawable.texture;
pass_descriptor.colorAttachments[0].loadAction = MTLLoadActionClear;
pass_descriptor.colorAttachments[0].storeAction = MTLStoreActionStore;
pass_descriptor.colorAttachments[0].clearColor = MTLClearColorMake(0, 0, 0, 1);
4. Crear render command encoder:
id<MTLRenderCommandEncoder> encoder =
[command_buffer renderCommandEncoderWithDescriptor:pass_descriptor];
5. Configurar pipeline y buffers:
[encoder setRenderPipelineState:pipeline_state_];
[encoder setVertexBuffer:vertex_buffer_ offset:0 atIndex:0];
[encoder setVertexBuffer:uniforms_buffer_ offset:0 atIndex:1];
[encoder setFragmentBuffer:uniforms_buffer_ offset:0 atIndex:1];
[encoder setFragmentTexture:game_texture offset:0 atIndex:0];
[encoder setFragmentSamplerState:sampler_state_ atIndex:0];
6. Dibujar:
[encoder drawIndexedPrimitives:MTLPrimitiveTypeTriangle
indexCount:6
indexType:MTLIndexTypeUInt16
indexBuffer:index_buffer_
indexBufferOffset:0];
7. Finalizar:
[encoder endEncoding];
[command_buffer presentDrawable:drawable];
[command_buffer commit];
Problema Crítico: Obtener MTLTexture desde SDL_Texture
SDL3 renderiza el juego a back_buffer_ (SDL_Texture). Necesitamos obtener
la textura Metal subyacente para pasarla al fragment shader.
Opciones:
-
SDL_GetProperty() - Usar SDL3 properties system:
id<MTLTexture> metal_texture = (__bridge id<MTLTexture>)SDL_GetProperty( SDL_GetTextureProperties(back_buffer_), "SDL.texture.metal.texture", nullptr ); -
Render to Metal texture directamente - En lugar de usar SDL_Texture, crear una MTLTexture directamente y renderizar el juego ahí. Más trabajo pero más control.
-
Copiar SDL texture a Metal texture - Menos eficiente pero más simple.
Sampler State
Falta crear el sampler state (equivalente a glTexParameteri en OpenGL):
MTLSamplerDescriptor* sampler_descriptor = [[MTLSamplerDescriptor alloc] init];
sampler_descriptor.minFilter = MTLSamplerMinMagFilterLinear;
sampler_descriptor.magFilter = MTLSamplerMinMagFilterLinear;
sampler_descriptor.sAddressMode = MTLSamplerAddressModeClampToEdge;
sampler_descriptor.tAddressMode = MTLSamplerAddressModeClampToEdge;
id<MTLSamplerState> sampler_state = [device_ newSamplerStateWithDescriptor:sampler_descriptor];
Build Configuration
Para compilar en Xcode/CMake, necesitarás:
-
CMakeLists.txt - Añadir metal_shader.mm:
if(APPLE) set(RENDERING_SOURCES ${RENDERING_SOURCES} source/rendering/metal/metal_shader.mm ) target_link_libraries(${PROJECT_NAME} "-framework Metal" "-framework QuartzCore" ) endif() -
Compilar shaders .metal - Opcionalmente pre-compilar:
xcrun -sdk macosx metal -c crtpi_vertex.metal -o crtpi_vertex.air xcrun -sdk macosx metal -c crtpi_fragment.metal -o crtpi_fragment.air xcrun -sdk macosx metallib crtpi_*.air -o crtpi.metallib -
Cargar .metallib en código:
NSString* path = [[NSBundle mainBundle] pathForResource:@"crtpi" ofType:@"metallib"]; id<MTLLibrary> library = [device_ newLibraryWithFile:path error:&error];
Testing en macOS
Cuando pruebes en macOS:
- Verifica que
SDL_WINDOW_METALestá activo en screen.cpp - Compila con
-DCMAKE_BUILD_TYPE=Debugpara ver logs - Usa Xcode Instruments (Metal Debugger) para inspeccionar frames
- Compara rendimiento con/sin shaders
Referencias Útiles
Próximos Pasos
- Implementar
render()completo - Resolver obtención de textura desde SDL
- Crear sampler state
- Testear en macOS real
- Optimizar si es necesario (probablemente ya será rápido)
Nota importante: Metal es significativamente más verboso que OpenGL pero también más eficiente. Una vez que funcione el render loop, el rendimiento debería ser excelente en macOS.