#include "rendering/sdl3gpu/sdl3gpu_shader_backend.hpp" #include #include "rendering/sdl3gpu/shader_factory.hpp" namespace Rendering { namespace { void logInfo(const std::string& msg) { std::cout << "[INFO] " << msg << '\n'; } void logError(const std::string& msg) { std::cerr << "[ERROR] " << msg << '\n'; } #ifdef __APPLE__ constexpr SDL_GPUShaderFormat SHADER_FORMAT = SDL_GPU_SHADERFORMAT_MSL; constexpr const char* VERTEX_ENTRY = "passthrough_vs"; constexpr const char* FRAGMENT_ENTRY = "test_fs"; // overridden per-shader (see loadShader) constexpr const char* VERTEX_SUFFIX = ".vert.msl"; constexpr const char* FRAGMENT_SUFFIX = ".frag.msl"; #else constexpr SDL_GPUShaderFormat SHADER_FORMAT = SDL_GPU_SHADERFORMAT_SPIRV; constexpr const char* VERTEX_ENTRY = "main"; constexpr const char* FRAGMENT_ENTRY = "main"; constexpr const char* VERTEX_SUFFIX = ".vert.spv"; constexpr const char* FRAGMENT_SUFFIX = ".frag.spv"; #endif } // namespace Sdl3GpuShaderBackend::~Sdl3GpuShaderBackend() { cleanup(); } auto Sdl3GpuShaderBackend::init(SDL_Window* window) -> bool { window_ = window; return createDevice(); } auto Sdl3GpuShaderBackend::createDevice() -> bool { device_ = SDL_CreateGPUDevice(SHADER_FORMAT, false, nullptr); if (device_ == nullptr) { logError(std::string("SDL_CreateGPUDevice failed: ") + SDL_GetError()); return false; } if (!SDL_ClaimWindowForGPUDevice(device_, window_)) { logError(std::string("SDL_ClaimWindowForGPUDevice failed: ") + SDL_GetError()); SDL_DestroyGPUDevice(device_); device_ = nullptr; return false; } SDL_SetGPUSwapchainParameters(device_, window_, SDL_GPU_SWAPCHAINCOMPOSITION_SDR, bestPresentMode()); const char* name = SDL_GetGPUDeviceDriver(device_); const std::string raw = (name != nullptr) ? name : "GPU"; if (raw == "vulkan") { driver_name_ = "Vulkan"; } else if (raw == "metal") { driver_name_ = "Metal"; } else if (raw == "d3d12") { driver_name_ = "D3D12"; } else if (!raw.empty()) { driver_name_ = raw; driver_name_[0] = static_cast(std::toupper(static_cast(driver_name_[0]))); } else { driver_name_ = "GPU"; } logInfo("GPU driver: " + driver_name_); return true; } auto Sdl3GpuShaderBackend::loadVertexShaderFor(const ShaderProgramSpec& spec) -> bool { if (vertex_shader_ != nullptr) { return true; } const std::filesystem::path common_dir = spec.folder.parent_path() / "_common"; const std::filesystem::path vertex_path = common_dir / (std::string("passthrough") + VERTEX_SUFFIX); vertex_shader_ = Sdl3Gpu::loadShaderFromFile(device_, vertex_path, SHADER_FORMAT, VERTEX_ENTRY, SDL_GPU_SHADERSTAGE_VERTEX, 0, 0); if (vertex_shader_ == nullptr) { logError("Failed to load shared vertex shader: " + vertex_path.string() + " (" + SDL_GetError() + ")"); return false; } logInfo("Loaded shared vertex shader: " + vertex_path.filename().string()); return true; } auto Sdl3GpuShaderBackend::buildPipeline(SDL_GPUShader* fragment) -> SDL_GPUGraphicsPipeline* { const SDL_GPUTextureFormat SWAPCHAIN_FORMAT = SDL_GetGPUSwapchainTextureFormat(device_, window_); SDL_GPUColorTargetBlendState no_blend{}; SDL_GPUColorTargetDescription color_target{}; color_target.format = SWAPCHAIN_FORMAT; color_target.blend_state = no_blend; SDL_GPUGraphicsPipelineCreateInfo info{}; info.vertex_shader = vertex_shader_; info.fragment_shader = fragment; info.primitive_type = SDL_GPU_PRIMITIVETYPE_TRIANGLELIST; info.target_info.num_color_targets = 1; info.target_info.color_target_descriptions = &color_target; SDL_GPUGraphicsPipeline* pipeline = SDL_CreateGPUGraphicsPipeline(device_, &info); if (pipeline == nullptr) { logError(std::string("SDL_CreateGPUGraphicsPipeline failed: ") + SDL_GetError()); } return pipeline; } auto Sdl3GpuShaderBackend::loadShader(const ShaderProgramSpec& spec) -> bool { if (device_ == nullptr) { return false; } if (!loadVertexShaderFor(spec)) { return false; } const std::filesystem::path frag_path = spec.folder / (spec.base_name + FRAGMENT_SUFFIX); #ifdef __APPLE__ const std::string entry = spec.base_name + "_fs"; const char* fragment_entry = entry.c_str(); #else const char* fragment_entry = FRAGMENT_ENTRY; #endif SDL_GPUShader* new_fragment = Sdl3Gpu::loadShaderFromFile(device_, frag_path, SHADER_FORMAT, fragment_entry, SDL_GPU_SHADERSTAGE_FRAGMENT, 0, 1); if (new_fragment == nullptr) { logError("Failed to load fragment shader: " + frag_path.string() + " (" + SDL_GetError() + ")"); return false; } SDL_GPUGraphicsPipeline* new_pipeline = buildPipeline(new_fragment); if (new_pipeline == nullptr) { SDL_ReleaseGPUShader(device_, new_fragment); return false; } SDL_WaitForGPUIdle(device_); if (pipeline_ != nullptr) { SDL_ReleaseGPUGraphicsPipeline(device_, pipeline_); } if (fragment_shader_ != nullptr) { SDL_ReleaseGPUShader(device_, fragment_shader_); } pipeline_ = new_pipeline; fragment_shader_ = new_fragment; logInfo("Shader loaded successfully: " + spec.base_name); return true; } void Sdl3GpuShaderBackend::render(const ShaderUniforms& uniforms) { if (device_ == nullptr || pipeline_ == nullptr) { return; } SDL_GPUCommandBuffer* cmd = SDL_AcquireGPUCommandBuffer(device_); if (cmd == nullptr) { return; } SDL_GPUTexture* swapchain = nullptr; Uint32 sw = 0; Uint32 sh = 0; if (!SDL_AcquireGPUSwapchainTexture(cmd, window_, &swapchain, &sw, &sh) || swapchain == nullptr) { SDL_SubmitGPUCommandBuffer(cmd); return; } SDL_GPUColorTargetInfo color_target{}; color_target.texture = swapchain; color_target.load_op = SDL_GPU_LOADOP_CLEAR; color_target.store_op = SDL_GPU_STOREOP_STORE; color_target.clear_color = {.r = 0.0f, .g = 0.0f, .b = 0.0f, .a = 1.0f}; SDL_GPURenderPass* pass = SDL_BeginGPURenderPass(cmd, &color_target, 1, nullptr); if (pass != nullptr) { SDL_GPUViewport vp{}; vp.x = 0.0f; vp.y = 0.0f; vp.w = static_cast(sw); vp.h = static_cast(sh); vp.min_depth = 0.0f; vp.max_depth = 1.0f; SDL_SetGPUViewport(pass, &vp); SDL_BindGPUGraphicsPipeline(pass, pipeline_); UniformsStd140 ubo{}; ubo.iTime = uniforms.iTime; ubo.iResolutionX = uniforms.iResolutionX; ubo.iResolutionY = uniforms.iResolutionY; SDL_PushGPUFragmentUniformData(cmd, 0, &ubo, sizeof(ubo)); SDL_DrawGPUPrimitives(pass, 3, 1, 0, 0); SDL_EndGPURenderPass(pass); } SDL_SubmitGPUCommandBuffer(cmd); } void Sdl3GpuShaderBackend::setVSync(bool vsync) { vsync_ = vsync; if (device_ != nullptr && window_ != nullptr) { SDL_SetGPUSwapchainParameters(device_, window_, SDL_GPU_SWAPCHAINCOMPOSITION_SDR, bestPresentMode()); logInfo(vsync ? "VSync enabled" : "VSync disabled"); } } auto Sdl3GpuShaderBackend::bestPresentMode() const -> SDL_GPUPresentMode { if (vsync_) { return SDL_GPU_PRESENTMODE_VSYNC; } if (device_ != nullptr && window_ != nullptr) { if (SDL_WindowSupportsGPUPresentMode(device_, window_, SDL_GPU_PRESENTMODE_IMMEDIATE)) { return SDL_GPU_PRESENTMODE_IMMEDIATE; } if (SDL_WindowSupportsGPUPresentMode(device_, window_, SDL_GPU_PRESENTMODE_MAILBOX)) { return SDL_GPU_PRESENTMODE_MAILBOX; } } return SDL_GPU_PRESENTMODE_VSYNC; } void Sdl3GpuShaderBackend::cleanup() { if (device_ == nullptr) { return; } SDL_WaitForGPUIdle(device_); if (pipeline_ != nullptr) { SDL_ReleaseGPUGraphicsPipeline(device_, pipeline_); pipeline_ = nullptr; } if (fragment_shader_ != nullptr) { SDL_ReleaseGPUShader(device_, fragment_shader_); fragment_shader_ = nullptr; } if (vertex_shader_ != nullptr) { SDL_ReleaseGPUShader(device_, vertex_shader_); vertex_shader_ = nullptr; } if (window_ != nullptr) { SDL_ReleaseWindowFromGPUDevice(device_, window_); } SDL_DestroyGPUDevice(device_); device_ = nullptr; window_ = nullptr; } auto makeSdl3GpuBackend() -> std::unique_ptr { return std::make_unique(); } } // namespace Rendering