#include "gpu_shader_preset.hpp" #include "gpu_texture.hpp" #include #include #include #include #include // ============================================================================ // Helpers // ============================================================================ static std::vector readFile(const std::string& path) { std::ifstream f(path, std::ios::binary | std::ios::ate); if (!f) return {}; std::streamsize sz = f.tellg(); f.seekg(0, std::ios::beg); std::vector buf(static_cast(sz)); if (!f.read(reinterpret_cast(buf.data()), sz)) return {}; return buf; } static std::string trim(const std::string& s) { size_t a = s.find_first_not_of(" \t\r\n"); if (a == std::string::npos) return {}; size_t b = s.find_last_not_of(" \t\r\n"); return s.substr(a, b - a + 1); } // ============================================================================ // GpuShaderPreset // ============================================================================ bool GpuShaderPreset::parseIni(const std::string& ini_path) { std::ifstream f(ini_path); if (!f) { SDL_Log("GpuShaderPreset: cannot open %s", ini_path.c_str()); return false; } int num_passes = 0; std::string line; while (std::getline(f, line)) { // Strip comments auto comment = line.find(';'); if (comment != std::string::npos) line = line.substr(0, comment); line = trim(line); if (line.empty()) continue; auto eq = line.find('='); if (eq == std::string::npos) continue; std::string key = trim(line.substr(0, eq)); std::string value = trim(line.substr(eq + 1)); if (key.empty() || value.empty()) continue; if (key == "name") { name_ = value; } else if (key == "passes") { num_passes = std::stoi(value); } else { // Try to parse as float parameter try { params_[key] = std::stof(value); } catch (...) { // Non-float values stored separately (pass0_vert etc.) } } } if (num_passes <= 0) { SDL_Log("GpuShaderPreset: no passes defined in %s", ini_path.c_str()); return false; } // Second pass: read per-pass file names f.clear(); f.seekg(0, std::ios::beg); descs_.resize(num_passes); while (std::getline(f, line)) { auto comment = line.find(';'); if (comment != std::string::npos) line = line.substr(0, comment); line = trim(line); if (line.empty()) continue; auto eq = line.find('='); if (eq == std::string::npos) continue; std::string key = trim(line.substr(0, eq)); std::string value = trim(line.substr(eq + 1)); for (int i = 0; i < num_passes; ++i) { std::string vi = "pass" + std::to_string(i) + "_vert"; std::string fi = "pass" + std::to_string(i) + "_frag"; if (key == vi) descs_[i].vert_name = value; if (key == fi) descs_[i].frag_name = value; } } // Validate for (int i = 0; i < num_passes; ++i) { if (descs_[i].vert_name.empty() || descs_[i].frag_name.empty()) { SDL_Log("GpuShaderPreset: pass %d missing vert or frag in %s", i, ini_path.c_str()); return false; } } return true; } SDL_GPUGraphicsPipeline* GpuShaderPreset::buildPassPipeline(SDL_GPUDevice* device, const std::string& vert_spv_path, const std::string& frag_spv_path, SDL_GPUTextureFormat target_fmt) { #ifdef __APPLE__ constexpr SDL_GPUShaderFormat kFmt = SDL_GPU_SHADERFORMAT_MSL; constexpr const char* kEntry = "main0"; #else constexpr SDL_GPUShaderFormat kFmt = SDL_GPU_SHADERFORMAT_SPIRV; constexpr const char* kEntry = "main"; #endif auto vert_spv = readFile(vert_spv_path); auto frag_spv = readFile(frag_spv_path); if (vert_spv.empty()) { SDL_Log("GpuShaderPreset: cannot read %s", vert_spv_path.c_str()); return nullptr; } if (frag_spv.empty()) { SDL_Log("GpuShaderPreset: cannot read %s", frag_spv_path.c_str()); return nullptr; } #ifdef __APPLE__ vert_spv.push_back(0); frag_spv.push_back(0); #endif SDL_GPUShaderCreateInfo vert_info = {}; vert_info.code = vert_spv.data(); vert_info.code_size = vert_spv.size(); vert_info.entrypoint = kEntry; vert_info.format = kFmt; vert_info.stage = SDL_GPU_SHADERSTAGE_VERTEX; vert_info.num_samplers = 0; vert_info.num_uniform_buffers = 0; SDL_GPUShaderCreateInfo frag_info = {}; frag_info.code = frag_spv.data(); frag_info.code_size = frag_spv.size(); frag_info.entrypoint = kEntry; frag_info.format = kFmt; frag_info.stage = SDL_GPU_SHADERSTAGE_FRAGMENT; frag_info.num_samplers = 1; frag_info.num_uniform_buffers = 1; SDL_GPUShader* vert_shader = SDL_CreateGPUShader(device, &vert_info); SDL_GPUShader* frag_shader = SDL_CreateGPUShader(device, &frag_info); if (!vert_shader || !frag_shader) { SDL_Log("GpuShaderPreset: shader creation failed for %s / %s: %s", vert_spv_path.c_str(), frag_spv_path.c_str(), SDL_GetError()); if (vert_shader) SDL_ReleaseGPUShader(device, vert_shader); if (frag_shader) SDL_ReleaseGPUShader(device, frag_shader); return nullptr; } // Full-screen triangle: no vertex input, no blend SDL_GPUColorTargetBlendState no_blend = {}; no_blend.enable_blend = false; no_blend.enable_color_write_mask = false; SDL_GPUColorTargetDescription ct_desc = {}; ct_desc.format = target_fmt; ct_desc.blend_state = no_blend; SDL_GPUVertexInputState no_input = {}; SDL_GPUGraphicsPipelineCreateInfo pipe_info = {}; pipe_info.vertex_shader = vert_shader; pipe_info.fragment_shader = frag_shader; pipe_info.vertex_input_state = no_input; pipe_info.primitive_type = SDL_GPU_PRIMITIVETYPE_TRIANGLELIST; pipe_info.target_info.num_color_targets = 1; pipe_info.target_info.color_target_descriptions = &ct_desc; SDL_GPUGraphicsPipeline* pipeline = SDL_CreateGPUGraphicsPipeline(device, &pipe_info); SDL_ReleaseGPUShader(device, vert_shader); SDL_ReleaseGPUShader(device, frag_shader); if (!pipeline) SDL_Log("GpuShaderPreset: pipeline creation failed: %s", SDL_GetError()); return pipeline; } bool GpuShaderPreset::load(SDL_GPUDevice* device, const std::string& dir, SDL_GPUTextureFormat swapchain_fmt, int w, int h) { dir_ = dir; swapchain_fmt_ = swapchain_fmt; // Parse ini if (!parseIni(dir + "/preset.ini")) return false; int n = static_cast(descs_.size()); passes_.resize(n); // Intermediate render target format (signed float to handle NTSC signal range) SDL_GPUTextureFormat inter_fmt = SDL_GPU_TEXTUREFORMAT_R16G16B16A16_FLOAT; for (int i = 0; i < n; ++i) { bool is_last = (i == n - 1); SDL_GPUTextureFormat target_fmt = is_last ? swapchain_fmt : inter_fmt; #ifdef __APPLE__ const char* ext = ".spv.msl"; #else const char* ext = ".spv"; #endif std::string vert_spv = dir + "/" + descs_[i].vert_name + ext; std::string frag_spv = dir + "/" + descs_[i].frag_name + ext; passes_[i].pipeline = buildPassPipeline(device, vert_spv, frag_spv, target_fmt); if (!passes_[i].pipeline) { SDL_Log("GpuShaderPreset: failed to build pipeline for pass %d", i); return false; } if (!is_last) { // Create intermediate render target auto tex = std::make_unique(); if (!tex->createRenderTarget(device, w, h, inter_fmt)) { SDL_Log("GpuShaderPreset: failed to create intermediate target for pass %d", i); return false; } passes_[i].target = tex.get(); targets_.push_back(std::move(tex)); } // Last pass: target = null (caller binds swapchain) } SDL_Log("GpuShaderPreset: loaded '%s' (%d passes)", name_.c_str(), n); return true; } void GpuShaderPreset::recreateTargets(SDL_GPUDevice* device, int w, int h) { SDL_GPUTextureFormat inter_fmt = SDL_GPU_TEXTUREFORMAT_R16G16B16A16_FLOAT; for (auto& tex : targets_) { tex->destroy(device); tex->createRenderTarget(device, w, h, inter_fmt); } } void GpuShaderPreset::destroy(SDL_GPUDevice* device) { for (auto& pass : passes_) { if (pass.pipeline) { SDL_ReleaseGPUGraphicsPipeline(device, pass.pipeline); pass.pipeline = nullptr; } } for (auto& tex : targets_) { if (tex) tex->destroy(device); } targets_.clear(); passes_.clear(); descs_.clear(); params_.clear(); } float GpuShaderPreset::param(const std::string& key, float default_val) const { auto it = params_.find(key); return (it != params_.end()) ? it->second : default_val; }