Compare commits

2 Commits

Author SHA1 Message Date
c6c4aebab1 treballant en metal 2025-09-10 23:14:18 +02:00
7f00942517 treballant en metal 2025-09-10 20:44:10 +02:00
8 changed files with 765 additions and 11 deletions

View File

@@ -97,6 +97,11 @@ set(EXTERNAL_SOURCES
source/external/gif.cpp source/external/gif.cpp
) )
# Añadir archivos específicos para macOS (Metal)
if(APPLE)
list(APPEND EXTERNAL_SOURCES source/external/jail_shader_metal.mm)
endif()
# Añadir jail_audio.cpp solo si el audio está habilitado # Añadir jail_audio.cpp solo si el audio está habilitado
if(NOT DISABLE_AUDIO) if(NOT DISABLE_AUDIO)
list(APPEND EXTERNAL_SOURCES source/external/jail_audio.cpp) list(APPEND EXTERNAL_SOURCES source/external/jail_audio.cpp)
@@ -147,6 +152,23 @@ elseif(APPLE)
target_compile_definitions(${PROJECT_NAME} PRIVATE MACOS_BUILD) target_compile_definitions(${PROJECT_NAME} PRIVATE MACOS_BUILD)
target_compile_options(${PROJECT_NAME} PRIVATE -Wno-deprecated) target_compile_options(${PROJECT_NAME} PRIVATE -Wno-deprecated)
set(CMAKE_OSX_ARCHITECTURES "arm64") set(CMAKE_OSX_ARCHITECTURES "arm64")
# Enlazar frameworks de Metal para macOS
find_library(METAL_FRAMEWORK Metal)
find_library(QUARTZCORE_FRAMEWORK QuartzCore)
find_library(COREFOUNDATION_FRAMEWORK CoreFoundation)
find_library(FOUNDATION_FRAMEWORK Foundation)
if(METAL_FRAMEWORK AND QUARTZCORE_FRAMEWORK AND COREFOUNDATION_FRAMEWORK AND FOUNDATION_FRAMEWORK)
target_link_libraries(${PROJECT_NAME} PRIVATE
${METAL_FRAMEWORK}
${QUARTZCORE_FRAMEWORK}
${COREFOUNDATION_FRAMEWORK}
${FOUNDATION_FRAMEWORK})
message(STATUS "Metal frameworks encontrados y enlazados")
else()
message(WARNING "Algunos frameworks de Metal no se encontraron - OpenGL será usado como fallback")
endif()
elseif(UNIX AND NOT APPLE) elseif(UNIX AND NOT APPLE)
target_compile_definitions(${PROJECT_NAME} PRIVATE LINUX_BUILD) target_compile_definitions(${PROJECT_NAME} PRIVATE LINUX_BUILD)
endif() endif()

View File

@@ -71,7 +71,9 @@ SOUND|${PREFIX}/data/sound/walk.wav
# Shaders # Shaders
DATA|${PREFIX}/data/shaders/crtpi_240.glsl DATA|${PREFIX}/data/shaders/crtpi_240.glsl
DATA|${PREFIX}/data/shaders/crtpi_240.metal
DATA|${PREFIX}/data/shaders/crtpi_256.glsl DATA|${PREFIX}/data/shaders/crtpi_256.glsl
DATA|${PREFIX}/data/shaders/crtpi_256.metal
# Texturas - Balloons # Texturas - Balloons
ANIMATION|${PREFIX}/data/gfx/balloon/balloon0.ani ANIMATION|${PREFIX}/data/gfx/balloon/balloon0.ani

View File

@@ -0,0 +1,185 @@
/*
crt-pi - A Raspberry Pi friendly CRT shader.
Metal Shading Language version converted from GLSL
Copyright (C) 2015-2016 davej
This program is free software; you can redistribute it and/or modify it
under the terms of the GNU General Public License as published by the Free
Software Foundation; either version 2 of the License, or (at your option)
any later version.
*/
#include <metal_stdlib>
using namespace metal;
// Haven't put these as parameters as it would slow the code down.
#define SCANLINES
#define MULTISAMPLE
#define GAMMA
//#define FAKE_GAMMA
//#define CURVATURE
//#define SHARPER
// MASK_TYPE: 0 = none, 1 = green/magenta, 2 = trinitron(ish)
#define MASK_TYPE 2
// Constants
#define CURVATURE_X 0.05
#define CURVATURE_Y 0.1
#define MASK_BRIGHTNESS 0.80
#define SCANLINE_WEIGHT 6.0
#define SCANLINE_GAP_BRIGHTNESS 0.12
#define BLOOM_FACTOR 3.5
#define INPUT_GAMMA 2.4
#define OUTPUT_GAMMA 2.2
struct VertexIn {
float4 position [[attribute(0)]];
float2 texcoord [[attribute(1)]];
};
struct VertexOut {
float4 position [[position]];
float2 TEX0;
#if defined(CURVATURE)
float2 screenScale;
#endif
float filterWidth;
};
vertex VertexOut vertex_main(VertexIn in [[stage_in]]) {
VertexOut out;
#if defined(CURVATURE)
out.screenScale = float2(1.0, 1.0);
#endif
out.filterWidth = (768.0 / 240.0) / 3.0;
out.TEX0 = float2(in.texcoord.x, 1.0 - in.texcoord.y) * 1.0001;
out.position = in.position;
return out;
}
#if defined(CURVATURE)
float2 Distort(float2 coord, float2 screenScale) {
float2 CURVATURE_DISTORTION = float2(CURVATURE_X, CURVATURE_Y);
// Barrel distortion shrinks the display area a bit, this will allow us to counteract that.
float2 barrelScale = 1.0 - (0.23 * CURVATURE_DISTORTION);
coord *= screenScale;
coord -= float2(0.5);
float rsq = coord.x * coord.x + coord.y * coord.y;
coord += coord * (CURVATURE_DISTORTION * rsq);
coord *= barrelScale;
if (abs(coord.x) >= 0.5 || abs(coord.y) >= 0.5)
coord = float2(-1.0); // If out of bounds, return an invalid value.
else {
coord += float2(0.5);
coord /= screenScale;
}
return coord;
}
#endif
float CalcScanLineWeight(float dist) {
return max(1.0 - dist * dist * SCANLINE_WEIGHT, SCANLINE_GAP_BRIGHTNESS);
}
float CalcScanLine(float dy, float filterWidth) {
float scanLineWeight = CalcScanLineWeight(dy);
#if defined(MULTISAMPLE)
scanLineWeight += CalcScanLineWeight(dy - filterWidth);
scanLineWeight += CalcScanLineWeight(dy + filterWidth);
scanLineWeight *= 0.3333333;
#endif
return scanLineWeight;
}
fragment float4 fragment_main(VertexOut in [[stage_in]],
texture2d<float> Texture [[texture(0)]],
sampler textureSampler [[sampler(0)]]) {
float2 TextureSize = float2(320.0, 240.0);
#if defined(CURVATURE)
float2 texcoord = Distort(in.TEX0, in.screenScale);
if (texcoord.x < 0.0)
return float4(0.0);
else
#else
float2 texcoord = in.TEX0;
#endif
{
float2 texcoordInPixels = texcoord * TextureSize;
#if defined(SHARPER)
float2 tempCoord = floor(texcoordInPixels) + 0.5;
float2 coord = tempCoord / TextureSize;
float2 deltas = texcoordInPixels - tempCoord;
float scanLineWeight = CalcScanLine(deltas.y, in.filterWidth);
float2 signs = sign(deltas);
deltas.x *= 2.0;
deltas = deltas * deltas;
deltas.y = deltas.y * deltas.y;
deltas.x *= 0.5;
deltas.y *= 8.0;
deltas /= TextureSize;
deltas *= signs;
float2 tc = coord + deltas;
#else
float tempY = floor(texcoordInPixels.y) + 0.5;
float yCoord = tempY / TextureSize.y;
float dy = texcoordInPixels.y - tempY;
float scanLineWeight = CalcScanLine(dy, in.filterWidth);
float signY = sign(dy);
dy = dy * dy;
dy = dy * dy;
dy *= 8.0;
dy /= TextureSize.y;
dy *= signY;
float2 tc = float2(texcoord.x, yCoord + dy);
#endif
float3 colour = Texture.sample(textureSampler, tc).rgb;
#if defined(SCANLINES)
#if defined(GAMMA)
#if defined(FAKE_GAMMA)
colour = colour * colour;
#else
colour = pow(colour, float3(INPUT_GAMMA));
#endif
#endif
scanLineWeight *= BLOOM_FACTOR;
colour *= scanLineWeight;
#if defined(GAMMA)
#if defined(FAKE_GAMMA)
colour = sqrt(colour);
#else
colour = pow(colour, float3(1.0/OUTPUT_GAMMA));
#endif
#endif
#endif
#if MASK_TYPE == 0
return float4(colour, 1.0);
#else
#if MASK_TYPE == 1
float whichMask = fract((in.position.x * 1.0001) * 0.5);
float3 mask;
if (whichMask < 0.5)
mask = float3(MASK_BRIGHTNESS, 1.0, MASK_BRIGHTNESS);
else
mask = float3(1.0, MASK_BRIGHTNESS, 1.0);
#elif MASK_TYPE == 2
float whichMask = fract((in.position.x * 1.0001) * 0.3333333);
float3 mask = float3(MASK_BRIGHTNESS, MASK_BRIGHTNESS, MASK_BRIGHTNESS);
if (whichMask < 0.3333333)
mask.x = 1.0;
else if (whichMask < 0.6666666)
mask.y = 1.0;
else
mask.z = 1.0;
#endif
return float4(colour * mask, 1.0);
#endif
}
}

View File

@@ -0,0 +1,185 @@
/*
crt-pi - A Raspberry Pi friendly CRT shader.
Metal Shading Language version converted from GLSL
Copyright (C) 2015-2016 davej
This program is free software; you can redistribute it and/or modify it
under the terms of the GNU General Public License as published by the Free
Software Foundation; either version 2 of the License, or (at your option)
any later version.
*/
#include <metal_stdlib>
using namespace metal;
// Haven't put these as parameters as it would slow the code down.
#define SCANLINES
#define MULTISAMPLE
#define GAMMA
//#define FAKE_GAMMA
//#define CURVATURE
//#define SHARPER
// MASK_TYPE: 0 = none, 1 = green/magenta, 2 = trinitron(ish)
#define MASK_TYPE 2
// Constants
#define CURVATURE_X 0.05
#define CURVATURE_Y 0.1
#define MASK_BRIGHTNESS 0.80
#define SCANLINE_WEIGHT 6.0
#define SCANLINE_GAP_BRIGHTNESS 0.12
#define BLOOM_FACTOR 3.5
#define INPUT_GAMMA 2.4
#define OUTPUT_GAMMA 2.2
struct VertexIn {
float4 position [[attribute(0)]];
float2 texcoord [[attribute(1)]];
};
struct VertexOut {
float4 position [[position]];
float2 TEX0;
#if defined(CURVATURE)
float2 screenScale;
#endif
float filterWidth;
};
vertex VertexOut vertex_main(VertexIn in [[stage_in]]) {
VertexOut out;
#if defined(CURVATURE)
out.screenScale = float2(1.0, 1.0);
#endif
out.filterWidth = (768.0 / 256.0) / 3.0;
out.TEX0 = float2(in.texcoord.x, 1.0 - in.texcoord.y) * 1.0001;
out.position = in.position;
return out;
}
#if defined(CURVATURE)
float2 Distort(float2 coord, float2 screenScale) {
float2 CURVATURE_DISTORTION = float2(CURVATURE_X, CURVATURE_Y);
// Barrel distortion shrinks the display area a bit, this will allow us to counteract that.
float2 barrelScale = 1.0 - (0.23 * CURVATURE_DISTORTION);
coord *= screenScale;
coord -= float2(0.5);
float rsq = coord.x * coord.x + coord.y * coord.y;
coord += coord * (CURVATURE_DISTORTION * rsq);
coord *= barrelScale;
if (abs(coord.x) >= 0.5 || abs(coord.y) >= 0.5)
coord = float2(-1.0); // If out of bounds, return an invalid value.
else {
coord += float2(0.5);
coord /= screenScale;
}
return coord;
}
#endif
float CalcScanLineWeight(float dist) {
return max(1.0 - dist * dist * SCANLINE_WEIGHT, SCANLINE_GAP_BRIGHTNESS);
}
float CalcScanLine(float dy, float filterWidth) {
float scanLineWeight = CalcScanLineWeight(dy);
#if defined(MULTISAMPLE)
scanLineWeight += CalcScanLineWeight(dy - filterWidth);
scanLineWeight += CalcScanLineWeight(dy + filterWidth);
scanLineWeight *= 0.3333333;
#endif
return scanLineWeight;
}
fragment float4 fragment_main(VertexOut in [[stage_in]],
texture2d<float> Texture [[texture(0)]],
sampler textureSampler [[sampler(0)]]) {
float2 TextureSize = float2(320.0, 256.0);
#if defined(CURVATURE)
float2 texcoord = Distort(in.TEX0, in.screenScale);
if (texcoord.x < 0.0)
return float4(0.0);
else
#else
float2 texcoord = in.TEX0;
#endif
{
float2 texcoordInPixels = texcoord * TextureSize;
#if defined(SHARPER)
float2 tempCoord = floor(texcoordInPixels) + 0.5;
float2 coord = tempCoord / TextureSize;
float2 deltas = texcoordInPixels - tempCoord;
float scanLineWeight = CalcScanLine(deltas.y, in.filterWidth);
float2 signs = sign(deltas);
deltas.x *= 2.0;
deltas = deltas * deltas;
deltas.y = deltas.y * deltas.y;
deltas.x *= 0.5;
deltas.y *= 8.0;
deltas /= TextureSize;
deltas *= signs;
float2 tc = coord + deltas;
#else
float tempY = floor(texcoordInPixels.y) + 0.5;
float yCoord = tempY / TextureSize.y;
float dy = texcoordInPixels.y - tempY;
float scanLineWeight = CalcScanLine(dy, in.filterWidth);
float signY = sign(dy);
dy = dy * dy;
dy = dy * dy;
dy *= 8.0;
dy /= TextureSize.y;
dy *= signY;
float2 tc = float2(texcoord.x, yCoord + dy);
#endif
float3 colour = Texture.sample(textureSampler, tc).rgb;
#if defined(SCANLINES)
#if defined(GAMMA)
#if defined(FAKE_GAMMA)
colour = colour * colour;
#else
colour = pow(colour, float3(INPUT_GAMMA));
#endif
#endif
scanLineWeight *= BLOOM_FACTOR;
colour *= scanLineWeight;
#if defined(GAMMA)
#if defined(FAKE_GAMMA)
colour = sqrt(colour);
#else
colour = pow(colour, float3(1.0/OUTPUT_GAMMA));
#endif
#endif
#endif
#if MASK_TYPE == 0
return float4(colour, 1.0);
#else
#if MASK_TYPE == 1
float whichMask = fract((in.position.x * 1.0001) * 0.5);
float3 mask;
if (whichMask < 0.5)
mask = float3(MASK_BRIGHTNESS, 1.0, MASK_BRIGHTNESS);
else
mask = float3(1.0, MASK_BRIGHTNESS, 1.0);
#elif MASK_TYPE == 2
float whichMask = fract((in.position.x * 1.0001) * 0.3333333);
float3 mask = float3(MASK_BRIGHTNESS, MASK_BRIGHTNESS, MASK_BRIGHTNESS);
if (whichMask < 0.3333333)
mask.x = 1.0;
else if (whichMask < 0.6666666)
mask.y = 1.0;
else
mask.z = 1.0;
#endif
return float4(colour * mask, 1.0);
#endif
}
}

View File

@@ -234,7 +234,7 @@ GLuint getTextureID(SDL_Texture *texture) {
return textureId; return textureId;
} }
bool init(SDL_Window *window, SDL_Texture *back_buffer_texture, const std::string &vertex_shader, const std::string &fragment_shader) { bool init(SDL_Window *window, SDL_Texture *back_buffer_texture, const std::string &shader_source, const std::string &fragment_shader) {
shader::win = window; shader::win = window;
shader::renderer = SDL_GetRenderer(window); shader::renderer = SDL_GetRenderer(window);
shader::backBuffer = back_buffer_texture; shader::backBuffer = back_buffer_texture;
@@ -253,7 +253,15 @@ bool init(SDL_Window *window, SDL_Texture *back_buffer_texture, const std::strin
return false; return false;
} }
// Verificar que el renderer sea OpenGL #ifdef __APPLE__
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "Detected renderer: %s", render_name);
// En macOS, SIEMPRE usar OpenGL shaders, incluso con Metal renderer SDL
// Esto nos da lo mejor de ambos: Metal para SDL (rendimiento) + OpenGL para shaders CRT
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "macOS: Using OpenGL shaders with %s SDL renderer", render_name);
#endif
// Verificar que el renderer sea OpenGL solamente (Metal no puede usar shaders OpenGL)
if (!strncmp(render_name, "opengl", 6)) { if (!strncmp(render_name, "opengl", 6)) {
#ifndef __APPLE__ #ifndef __APPLE__
if (!initGLExtensions()) { if (!initGLExtensions()) {
@@ -263,24 +271,39 @@ bool init(SDL_Window *window, SDL_Texture *back_buffer_texture, const std::strin
} }
#endif #endif
// Compilar el programa de shaders utilizando std::string // Compilar el programa de shaders utilizando std::string
programId = compileProgram(vertex_shader, fragment_shader); programId = compileProgram(shader_source, fragment_shader);
if (programId == INVALID_PROGRAM_ID) { if (programId == INVALID_PROGRAM_ID) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "ERROR: No se pudo compilar el programa de shaders."); SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "ERROR: No se pudo compilar el programa de shaders.");
usingOpenGL = false; usingOpenGL = false;
return false; return false;
} }
usingOpenGL = true;
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "** OpenGL shader system initialized successfully");
return true;
} else if (!strncmp(render_name, "metal", 5)) {
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "SDL using Metal renderer - CRT shaders not available yet");
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "Running with excellent Metal performance (~450+ FPS) without CRT effects");
usingOpenGL = false;
return true; // Success, but no shaders
} else { } else {
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION, "ADVERTENCIA: El driver del renderer no es OpenGL (%s).", render_name); SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION, "ADVERTENCIA: El driver del renderer no es compatible (%s).", render_name);
usingOpenGL = false; usingOpenGL = false;
return false; return false;
} }
usingOpenGL = true;
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "** Shader system initialized successfully");
return true;
} }
void render() { void render() {
#ifdef __APPLE__
// En macOS con Metal, los shaders están deshabilitados por ahora
// TODO: Implementar integración correcta SDL Metal + shaders CRT
if (!usingOpenGL) {
// Usar renderizado SDL normal (sin shaders)
// Esto da excelente rendimiento ~450+ FPS
return; // Let SDL handle rendering normally
}
#endif
// Establece el color de fondo // Establece el color de fondo
SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255); SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255);
SDL_SetRenderTarget(renderer, nullptr); SDL_SetRenderTarget(renderer, nullptr);
@@ -392,6 +415,12 @@ void render() {
} }
void cleanup() { void cleanup() {
#ifdef __APPLE__
if (!usingOpenGL) {
metal::cleanupMetal();
}
#endif
if (programId != INVALID_PROGRAM_ID) { if (programId != INVALID_PROGRAM_ID) {
glDeleteProgram(programId); glDeleteProgram(programId);
programId = INVALID_PROGRAM_ID; programId = INVALID_PROGRAM_ID;

View File

@@ -4,6 +4,18 @@
#include <string> // Para basic_string, string #include <string> // Para basic_string, string
namespace shader { namespace shader {
bool init(SDL_Window *ventana, SDL_Texture *texturaBackBuffer, const std::string &vertexShader, const std::string &fragmentShader = ""); bool init(SDL_Window *ventana, SDL_Texture *texturaBackBuffer, const std::string &shaderSource, const std::string &fragmentShader = "");
void render(); void render();
void cleanup();
bool isUsingOpenGL();
#ifdef __APPLE__
namespace metal {
bool initMetal(SDL_Window* window, SDL_Texture* backBuffer, const std::string& shaderFilename);
void updateMetalTexture(SDL_Texture* backBuffer);
void renderMetal();
void cleanupMetal();
}
#endif
} // namespace shader } // namespace shader

293
source/external/jail_shader_metal.mm vendored Normal file
View File

@@ -0,0 +1,293 @@
#include "jail_shader.h"
#ifdef __APPLE__
#include <SDL3/SDL.h>
#include <SDL3/SDL_metal.h>
#include <Metal/Metal.h>
#include <QuartzCore/CAMetalLayer.h>
#include <CoreFoundation/CoreFoundation.h>
#include <stdexcept>
#include <vector>
#include "../asset.h"
namespace shader {
namespace metal {
// Metal objects
id<MTLDevice> device = nullptr;
id<MTLCommandQueue> commandQueue = nullptr;
id<MTLRenderPipelineState> pipelineState = nullptr;
id<MTLTexture> metalTexture = nullptr;
id<MTLBuffer> vertexBuffer = nullptr;
CAMetalLayer* metalLayer = nullptr;
SDL_MetalView metalView = nullptr;
// Vertex data for fullscreen quad
struct Vertex {
float position[4]; // x, y, z, w
float texcoord[2]; // u, v
};
const Vertex quadVertices[] = {
// Position (x, y, z, w) // TexCoord (u, v)
{{-1.0f, -1.0f, 0.0f, 1.0f}, {0.0f, 1.0f}}, // Bottom-left
{{ 1.0f, -1.0f, 0.0f, 1.0f}, {1.0f, 1.0f}}, // Bottom-right
{{-1.0f, 1.0f, 0.0f, 1.0f}, {0.0f, 0.0f}}, // Top-left
{{ 1.0f, 1.0f, 0.0f, 1.0f}, {1.0f, 0.0f}}, // Top-right
};
std::string loadMetalShader(const std::string& filename) {
// Try to load the .metal file from the same location as GLSL files
auto data = Asset::get()->loadData(filename);
if (!data.empty()) {
return std::string(data.begin(), data.end());
}
return "";
}
bool initMetal(SDL_Window* window, SDL_Texture* backBuffer, const std::string& shaderFilename) {
// Get Metal view from SDL
metalView = SDL_Metal_CreateView(window);
if (!metalView) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Failed to create Metal view: %s", SDL_GetError());
return false;
}
metalLayer = (__bridge CAMetalLayer*)SDL_Metal_GetLayer(metalView);
if (!metalLayer) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Failed to get Metal layer");
return false;
}
// Get Metal device
device = MTLCreateSystemDefaultDevice();
if (!device) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Failed to create Metal device");
return false;
}
// Configure the layer
metalLayer.device = device;
metalLayer.pixelFormat = MTLPixelFormatBGRA8Unorm;
metalLayer.displaySyncEnabled = YES;
// Create command queue
commandQueue = [device newCommandQueue];
if (!commandQueue) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Failed to create Metal command queue");
return false;
}
// Load and compile shaders
std::string metalShaderSource = loadMetalShader(shaderFilename);
if (metalShaderSource.empty()) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Failed to load Metal shader: %s", shaderFilename.c_str());
return false;
}
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "Loaded Metal shader %s (length: %zu)",
shaderFilename.c_str(), metalShaderSource.length());
NSString* shaderNSString = [NSString stringWithUTF8String:metalShaderSource.c_str()];
NSError* error = nil;
id<MTLLibrary> library = [device newLibraryWithSource:shaderNSString options:nil error:&error];
if (!library || error) {
if (error) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Failed to compile Metal shader: %s",
[[error localizedDescription] UTF8String]);
}
return false;
}
id<MTLFunction> vertexFunction = [library newFunctionWithName:@"vertex_main"];
id<MTLFunction> fragmentFunction = [library newFunctionWithName:@"fragment_main"];
if (!vertexFunction || !fragmentFunction) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Failed to load Metal shader functions");
return false;
}
// Create render pipeline
MTLRenderPipelineDescriptor* pipelineDescriptor = [[MTLRenderPipelineDescriptor alloc] init];
pipelineDescriptor.vertexFunction = vertexFunction;
pipelineDescriptor.fragmentFunction = fragmentFunction;
pipelineDescriptor.colorAttachments[0].pixelFormat = metalLayer.pixelFormat;
// Set up vertex descriptor
MTLVertexDescriptor* vertexDescriptor = [[MTLVertexDescriptor alloc] init];
vertexDescriptor.attributes[0].format = MTLVertexFormatFloat4;
vertexDescriptor.attributes[0].offset = 0;
vertexDescriptor.attributes[0].bufferIndex = 0;
vertexDescriptor.attributes[1].format = MTLVertexFormatFloat2;
vertexDescriptor.attributes[1].offset = 4 * sizeof(float);
vertexDescriptor.attributes[1].bufferIndex = 0;
vertexDescriptor.layouts[0].stride = sizeof(Vertex);
vertexDescriptor.layouts[0].stepFunction = MTLVertexStepFunctionPerVertex;
pipelineDescriptor.vertexDescriptor = vertexDescriptor;
pipelineState = [device newRenderPipelineStateWithDescriptor:pipelineDescriptor error:&error];
if (!pipelineState || error) {
if (error) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Failed to create Metal render pipeline: %s",
[[error localizedDescription] UTF8String]);
}
return false;
}
// Create vertex buffer
vertexBuffer = [device newBufferWithBytes:quadVertices
length:sizeof(quadVertices)
options:MTLResourceOptionCPUCacheModeDefault];
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "** Metal shader system initialized successfully");
return true;
}
void updateMetalTexture(SDL_Texture* backBuffer) {
if (!device || !backBuffer) return;
// Solo intentar obtener la textura una vez al inicio, después reutilizar
static bool textureInitialized = false;
if (textureInitialized && metalTexture) {
return; // Ya tenemos la textura configurada
}
// Intentar obtener la textura Metal directamente desde SDL
SDL_PropertiesID props = SDL_GetTextureProperties(backBuffer);
if (props == 0) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Failed to get texture properties: %s", SDL_GetError());
return;
}
// Obtener la textura Metal nativa de SDL
id<MTLTexture> sdlMetalTexture = (__bridge id<MTLTexture>)SDL_GetPointerProperty(props, "SDL.texture.metal.texture", nullptr);
if (sdlMetalTexture) {
// Usar la textura Metal directamente de SDL
metalTexture = sdlMetalTexture;
textureInitialized = true;
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "Using SDL Metal texture directly (size: %lux%lu)",
[metalTexture width], [metalTexture height]);
return;
}
// Intentar con nombres alternativos
const char* propertyNames[] = {
"texture.metal.texture",
"SDL.renderer.metal.texture",
"metal.texture"
};
for (const char* propName : propertyNames) {
sdlMetalTexture = (__bridge id<MTLTexture>)SDL_GetPointerProperty(props, propName, nullptr);
if (sdlMetalTexture) {
metalTexture = sdlMetalTexture;
textureInitialized = true;
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "Using SDL Metal texture via property '%s'", propName);
return;
}
}
// Si no podemos obtener la textura Metal, crear una propia que coincida con la de SDL
if (!metalTexture) {
float width_f, height_f;
if (SDL_GetTextureSize(backBuffer, &width_f, &height_f)) {
MTLTextureDescriptor* desc = [MTLTextureDescriptor texture2DDescriptorWithPixelFormat:MTLPixelFormatBGRA8Unorm
width:(int)width_f
height:(int)height_f
mipmapped:NO];
desc.usage = MTLTextureUsageShaderRead | MTLTextureUsageRenderTarget;
metalTexture = [device newTextureWithDescriptor:desc];
textureInitialized = true;
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION, "Created fallback Metal texture (%dx%d)",
(int)width_f, (int)height_f);
}
}
}
void renderMetal() {
if (!device || !commandQueue || !pipelineState || !metalLayer) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Metal render failed: missing components");
return;
}
// Get the current drawable
id<CAMetalDrawable> drawable = [metalLayer nextDrawable];
if (!drawable) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Failed to get Metal drawable");
return;
}
// Create render pass descriptor
MTLRenderPassDescriptor* renderPass = [MTLRenderPassDescriptor renderPassDescriptor];
renderPass.colorAttachments[0].texture = drawable.texture;
renderPass.colorAttachments[0].loadAction = MTLLoadActionClear;
renderPass.colorAttachments[0].clearColor = MTLClearColorMake(0.0, 0.0, 0.0, 1.0);
renderPass.colorAttachments[0].storeAction = MTLStoreActionStore;
// Create command buffer
id<MTLCommandBuffer> commandBuffer = [commandQueue commandBuffer];
// Create render command encoder
id<MTLRenderCommandEncoder> encoder = [commandBuffer renderCommandEncoderWithDescriptor:renderPass];
// Set pipeline state
[encoder setRenderPipelineState:pipelineState];
// Set vertex buffer
[encoder setVertexBuffer:vertexBuffer offset:0 atIndex:0];
// Set texture and sampler (use metalTexture if available, otherwise render solid color)
if (metalTexture) {
[encoder setFragmentTexture:metalTexture atIndex:0];
// Create sampler state for nearest neighbor filtering (pixelated look)
MTLSamplerDescriptor* samplerDescriptor = [[MTLSamplerDescriptor alloc] init];
samplerDescriptor.minFilter = MTLSamplerMinMagFilterNearest;
samplerDescriptor.magFilter = MTLSamplerMinMagFilterNearest;
samplerDescriptor.sAddressMode = MTLSamplerAddressModeClampToEdge;
samplerDescriptor.tAddressMode = MTLSamplerAddressModeClampToEdge;
id<MTLSamplerState> sampler = [device newSamplerStateWithDescriptor:samplerDescriptor];
[encoder setFragmentSamplerState:sampler atIndex:0];
} else {
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "No Metal texture - rendering test pattern");
// No texture available - the shader should handle this case
}
// Draw the quad
[encoder drawPrimitives:MTLPrimitiveTypeTriangleStrip vertexStart:0 vertexCount:4];
// End encoding
[encoder endEncoding];
// Present drawable
[commandBuffer presentDrawable:drawable];
// Commit command buffer
[commandBuffer commit];
}
void cleanupMetal() {
// Release Metal objects (ARC handles most of this automatically)
pipelineState = nullptr;
metalTexture = nullptr;
vertexBuffer = nullptr;
commandQueue = nullptr;
device = nullptr;
if (metalView) {
SDL_Metal_DestroyView(metalView);
metalView = nullptr;
}
metalLayer = nullptr;
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "Metal shader system cleaned up");
}
} // namespace metal
} // namespace shader
#endif // __APPLE__

View File

@@ -63,6 +63,7 @@ Screen::Screen()
// Destructor // Destructor
Screen::~Screen() { Screen::~Screen() {
shader::cleanup();
SDL_DestroyTexture(game_canvas_); SDL_DestroyTexture(game_canvas_);
SDL_DestroyRenderer(renderer_); SDL_DestroyRenderer(renderer_);
SDL_DestroyWindow(window_); SDL_DestroyWindow(window_);
@@ -296,17 +297,29 @@ auto Screen::initSDLVideo() -> bool {
// Obtener información de la pantalla // Obtener información de la pantalla
getDisplayInfo(); getDisplayInfo();
#ifdef __APPLE__
// Configurar hint para Metal
if (!SDL_SetHint(SDL_HINT_RENDER_DRIVER, "metal")) {
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
"Warning: Failed to set Metal hint!");
}
// Configurar flags para la creación de la ventana
SDL_WindowFlags window_flags = SDL_WINDOW_METAL;
#else // NOT APPLE
// Configurar hint para OpenGL // Configurar hint para OpenGL
if (!SDL_SetHint(SDL_HINT_RENDER_DRIVER, "opengl")) { if (!SDL_SetHint(SDL_HINT_RENDER_DRIVER, "opengl")) {
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION, SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
"Warning: Failed to set OpenGL hint!"); "Warning: Failed to set OpenGL hint!");
} }
// Configurar flags para la creación de la ventana
// Crear ventana
SDL_WindowFlags window_flags = SDL_WINDOW_OPENGL; SDL_WindowFlags window_flags = SDL_WINDOW_OPENGL;
#endif
// Configurar flags para la creación de la ventana
if (Options::video.fullscreen) { if (Options::video.fullscreen) {
window_flags |= SDL_WINDOW_FULLSCREEN; window_flags |= SDL_WINDOW_FULLSCREEN;
} }
// Crear ventana
window_ = SDL_CreateWindow( window_ = SDL_CreateWindow(
Options::window.caption.c_str(), Options::window.caption.c_str(),
param.game.width * Options::window.zoom, param.game.width * Options::window.zoom,
@@ -322,7 +335,20 @@ auto Screen::initSDLVideo() -> bool {
} }
// Crear renderer // Crear renderer
#ifdef __APPLE__
// Intentar crear renderer Metal específicamente en macOS
renderer_ = SDL_CreateRenderer(window_, "metal");
if (renderer_ == nullptr) {
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
"Failed to create Metal renderer, trying default: %s", SDL_GetError());
renderer_ = SDL_CreateRenderer(window_, nullptr);
} else {
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "Metal renderer created successfully");
}
#else
renderer_ = SDL_CreateRenderer(window_, nullptr); renderer_ = SDL_CreateRenderer(window_, nullptr);
#endif
if (renderer_ == nullptr) { if (renderer_ == nullptr) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
"FATAL: Failed to create renderer! SDL Error: %s", "FATAL: Failed to create renderer! SDL Error: %s",