Files
coffee_crisis_arcade_edition/source/rendering/metal/METAL_BACKEND_NOTES.md
Sergio Valor d6ffbda00d WIP: Metal shader backend para macOS
- 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.
2025-10-02 21:05:28 +02:00

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 MetalShader class
  • 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:

  1. SDL_GetProperty() - Usar SDL3 properties system:

    id<MTLTexture> metal_texture = (__bridge id<MTLTexture>)SDL_GetProperty(
        SDL_GetTextureProperties(back_buffer_),
        "SDL.texture.metal.texture",
        nullptr
    );
    
  2. 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.

  3. 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:

  1. 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()
    
  2. 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
    
  3. 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:

  1. Verifica que SDL_WINDOW_METAL está activo en screen.cpp
  2. Compila con -DCMAKE_BUILD_TYPE=Debug para ver logs
  3. Usa Xcode Instruments (Metal Debugger) para inspeccionar frames
  4. Compara rendimiento con/sin shaders

Referencias Útiles

Próximos Pasos

  1. Implementar render() completo
  2. Resolver obtención de textura desde SDL
  3. Crear sampler state
  4. Testear en macOS real
  5. 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.