backend-infra-engineer: Release v0.3.9-hotfix7 snapshot

This commit is contained in:
scawful
2025-11-23 13:37:10 -05:00
parent c8289bffda
commit 2934c82b75
202 changed files with 34914 additions and 845 deletions

293
src/app/platform/iwindow.h Normal file
View File

@@ -0,0 +1,293 @@
// iwindow.h - Window Backend Abstraction Layer
// Provides interface for swapping window implementations (SDL2, SDL3)
#ifndef YAZE_APP_PLATFORM_IWINDOW_H_
#define YAZE_APP_PLATFORM_IWINDOW_H_
#include <cstdint>
#include <memory>
#include <string>
#include "absl/status/status.h"
#include "app/gfx/backend/irenderer.h"
// Forward declarations to avoid SDL header dependency in interface
struct SDL_Window;
namespace yaze {
namespace platform {
/**
* @brief Window configuration parameters
*/
struct WindowConfig {
std::string title = "Yet Another Zelda3 Editor";
int width = 0; // 0 means auto-detect from display
int height = 0; // 0 means auto-detect from display
float display_scale = 0.8f; // Percentage of display to use when auto-detect
bool resizable = true;
bool maximized = false;
bool fullscreen = false;
bool high_dpi = true;
};
/**
* @brief Window event types (platform-agnostic)
*/
enum class WindowEventType {
None,
Close,
Resize,
Minimized,
Maximized,
Restored,
Shown,
Hidden,
Exposed,
FocusGained,
FocusLost,
KeyDown,
KeyUp,
MouseMotion,
MouseButtonDown,
MouseButtonUp,
MouseWheel,
Quit,
DropFile
};
/**
* @brief Platform-agnostic window event data
*/
struct WindowEvent {
WindowEventType type = WindowEventType::None;
// Window resize data
int window_width = 0;
int window_height = 0;
// Keyboard data
int key_code = 0;
int scan_code = 0;
bool key_shift = false;
bool key_ctrl = false;
bool key_alt = false;
bool key_super = false;
// Mouse data
float mouse_x = 0.0f;
float mouse_y = 0.0f;
int mouse_button = 0;
float wheel_x = 0.0f;
float wheel_y = 0.0f;
// Drop file data
std::string dropped_file;
};
/**
* @brief Window backend status information
*/
struct WindowStatus {
bool is_active = true;
bool is_minimized = false;
bool is_maximized = false;
bool is_fullscreen = false;
bool is_focused = true;
bool is_resizing = false;
int width = 0;
int height = 0;
};
/**
* @brief Abstract window backend interface
*
* Provides platform-agnostic window management, allowing different
* SDL versions or other windowing libraries to be swapped without
* changing application code.
*/
class IWindowBackend {
public:
virtual ~IWindowBackend() = default;
// =========================================================================
// Lifecycle Management
// =========================================================================
/**
* @brief Initialize the window backend with configuration
* @param config Window configuration parameters
* @return Status indicating success or failure
*/
virtual absl::Status Initialize(const WindowConfig& config) = 0;
/**
* @brief Shutdown the window backend and release resources
* @return Status indicating success or failure
*/
virtual absl::Status Shutdown() = 0;
/**
* @brief Check if the backend is initialized
*/
virtual bool IsInitialized() const = 0;
// =========================================================================
// Event Processing
// =========================================================================
/**
* @brief Poll and process pending events
* @param out_event Output parameter for the next event
* @return True if an event was available, false otherwise
*/
virtual bool PollEvent(WindowEvent& out_event) = 0;
/**
* @brief Process a native SDL event (for ImGui integration)
* @param native_event Pointer to native SDL_Event
*/
virtual void ProcessNativeEvent(void* native_event) = 0;
// =========================================================================
// Window State
// =========================================================================
/**
* @brief Get current window status
*/
virtual WindowStatus GetStatus() const = 0;
/**
* @brief Check if window is still active (not closed)
*/
virtual bool IsActive() const = 0;
/**
* @brief Set window active state
*/
virtual void SetActive(bool active) = 0;
/**
* @brief Get window dimensions
*/
virtual void GetSize(int* width, int* height) const = 0;
/**
* @brief Set window dimensions
*/
virtual void SetSize(int width, int height) = 0;
/**
* @brief Get window title
*/
virtual std::string GetTitle() const = 0;
/**
* @brief Set window title
*/
virtual void SetTitle(const std::string& title) = 0;
// =========================================================================
// Renderer Integration
// =========================================================================
/**
* @brief Initialize renderer for this window
* @param renderer The renderer to initialize
* @return True if successful
*/
virtual bool InitializeRenderer(gfx::IRenderer* renderer) = 0;
/**
* @brief Get the underlying SDL_Window pointer for ImGui integration
* @return Native window handle (SDL_Window*)
*/
virtual SDL_Window* GetNativeWindow() = 0;
// =========================================================================
// ImGui Integration
// =========================================================================
/**
* @brief Initialize ImGui backends for this window/renderer combo
* @param renderer The renderer (for backend-specific init)
* @return Status indicating success or failure
*/
virtual absl::Status InitializeImGui(gfx::IRenderer* renderer) = 0;
/**
* @brief Shutdown ImGui backends
*/
virtual void ShutdownImGui() = 0;
/**
* @brief Start a new ImGui frame
*/
virtual void NewImGuiFrame() = 0;
// =========================================================================
// Audio Support (Legacy compatibility)
// =========================================================================
/**
* @brief Get audio device ID (for legacy audio buffer management)
*/
virtual uint32_t GetAudioDevice() const = 0;
/**
* @brief Get audio buffer (for legacy audio management)
*/
virtual std::shared_ptr<int16_t> GetAudioBuffer() const = 0;
// =========================================================================
// Backend Information
// =========================================================================
/**
* @brief Get backend name for debugging/logging
*/
virtual std::string GetBackendName() const = 0;
/**
* @brief Get SDL version being used
*/
virtual int GetSDLVersion() const = 0;
};
/**
* @brief Backend type enumeration for factory
*/
enum class WindowBackendType {
SDL2,
SDL3,
Auto // Automatically select based on availability
};
/**
* @brief Factory for creating window backends
*/
class WindowBackendFactory {
public:
/**
* @brief Create a window backend of the specified type
* @param type The type of backend to create
* @return Unique pointer to the created backend
*/
static std::unique_ptr<IWindowBackend> Create(WindowBackendType type);
/**
* @brief Get the default backend type for this build
*/
static WindowBackendType GetDefaultType();
/**
* @brief Check if a backend type is available
*/
static bool IsAvailable(WindowBackendType type);
};
} // namespace platform
} // namespace yaze
#endif // YAZE_APP_PLATFORM_IWINDOW_H_

View File

@@ -0,0 +1,435 @@
// sdl2_window_backend.cc - SDL2 Window Backend Implementation
#include "app/platform/sdl2_window_backend.h"
#include "app/platform/sdl_compat.h"
#include <filesystem>
#include "absl/status/status.h"
#include "absl/strings/str_format.h"
#include "app/gfx/resource/arena.h"
#include "app/gui/core/style.h"
#include "app/platform/font_loader.h"
#include "imgui/backends/imgui_impl_sdl2.h"
#include "imgui/backends/imgui_impl_sdlrenderer2.h"
#include "imgui/imgui.h"
#include "util/log.h"
namespace yaze {
namespace platform {
// Global flag for window resize state (used by emulator to pause)
// This maintains compatibility with the legacy window.cc
extern bool g_window_is_resizing;
SDL2WindowBackend::~SDL2WindowBackend() {
if (initialized_) {
Shutdown();
}
}
absl::Status SDL2WindowBackend::Initialize(const WindowConfig& config) {
if (initialized_) {
LOG_WARN("SDL2WindowBackend", "Already initialized, shutting down first");
RETURN_IF_ERROR(Shutdown());
}
// Initialize SDL2 subsystems
if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_TIMER) != 0) {
return absl::InternalError(
absl::StrFormat("SDL_Init failed: %s", SDL_GetError()));
}
// Determine window size
int screen_width = config.width;
int screen_height = config.height;
if (screen_width == 0 || screen_height == 0) {
// Auto-detect from display
SDL_DisplayMode display_mode;
if (SDL_GetCurrentDisplayMode(0, &display_mode) == 0) {
screen_width = static_cast<int>(display_mode.w * config.display_scale);
screen_height = static_cast<int>(display_mode.h * config.display_scale);
} else {
// Fallback to reasonable defaults
screen_width = 1280;
screen_height = 720;
LOG_WARN("SDL2WindowBackend",
"Failed to get display mode, using defaults: %dx%d",
screen_width, screen_height);
}
}
// Build window flags
Uint32 flags = 0;
if (config.resizable) {
flags |= SDL_WINDOW_RESIZABLE;
}
if (config.maximized) {
flags |= SDL_WINDOW_MAXIMIZED;
}
if (config.fullscreen) {
flags |= SDL_WINDOW_FULLSCREEN_DESKTOP;
}
if (config.high_dpi) {
flags |= SDL_WINDOW_ALLOW_HIGHDPI;
}
// Create window
window_ = std::unique_ptr<SDL_Window, util::SDL_Deleter>(
SDL_CreateWindow(config.title.c_str(), SDL_WINDOWPOS_UNDEFINED,
SDL_WINDOWPOS_UNDEFINED, screen_width, screen_height,
flags),
util::SDL_Deleter());
if (!window_) {
SDL_Quit();
return absl::InternalError(
absl::StrFormat("SDL_CreateWindow failed: %s", SDL_GetError()));
}
// Allocate legacy audio buffer for backwards compatibility
const int audio_frequency = 48000;
const size_t buffer_size = (audio_frequency / 50) * 2; // Stereo PAL
audio_buffer_ = std::shared_ptr<int16_t>(new int16_t[buffer_size],
std::default_delete<int16_t[]>());
LOG_INFO("SDL2WindowBackend",
"Initialized: %dx%d, audio buffer: %zu samples", screen_width,
screen_height, buffer_size);
initialized_ = true;
active_ = true;
return absl::OkStatus();
}
absl::Status SDL2WindowBackend::Shutdown() {
if (!initialized_) {
return absl::OkStatus();
}
// Pause and close audio device if open
if (audio_device_ != 0) {
SDL_PauseAudioDevice(audio_device_, 1);
SDL_CloseAudioDevice(audio_device_);
audio_device_ = 0;
}
// Shutdown ImGui if initialized
if (imgui_initialized_) {
ShutdownImGui();
}
// Shutdown graphics arena while renderer is still valid
LOG_INFO("SDL2WindowBackend", "Shutting down graphics arena...");
gfx::Arena::Get().Shutdown();
// Destroy window
if (window_) {
LOG_INFO("SDL2WindowBackend", "Destroying window...");
window_.reset();
}
// Quit SDL
LOG_INFO("SDL2WindowBackend", "Shutting down SDL...");
SDL_Quit();
initialized_ = false;
LOG_INFO("SDL2WindowBackend", "Shutdown complete");
return absl::OkStatus();
}
bool SDL2WindowBackend::PollEvent(WindowEvent& out_event) {
SDL_Event sdl_event;
if (SDL_PollEvent(&sdl_event)) {
// Let ImGui process the event first
if (imgui_initialized_) {
ImGui_ImplSDL2_ProcessEvent(&sdl_event);
}
// Convert to platform-agnostic event
out_event = ConvertSDL2Event(sdl_event);
return true;
}
return false;
}
void SDL2WindowBackend::ProcessNativeEvent(void* native_event) {
if (native_event && imgui_initialized_) {
ImGui_ImplSDL2_ProcessEvent(static_cast<SDL_Event*>(native_event));
}
}
WindowEvent SDL2WindowBackend::ConvertSDL2Event(const SDL_Event& sdl_event) {
WindowEvent event;
event.type = WindowEventType::None;
switch (sdl_event.type) {
case SDL_QUIT:
event.type = WindowEventType::Quit;
active_ = false;
break;
case SDL_KEYDOWN:
event.type = WindowEventType::KeyDown;
event.key_code = sdl_event.key.keysym.sym;
event.scan_code = sdl_event.key.keysym.scancode;
UpdateModifierState();
event.key_shift = key_shift_;
event.key_ctrl = key_ctrl_;
event.key_alt = key_alt_;
event.key_super = key_super_;
break;
case SDL_KEYUP:
event.type = WindowEventType::KeyUp;
event.key_code = sdl_event.key.keysym.sym;
event.scan_code = sdl_event.key.keysym.scancode;
UpdateModifierState();
event.key_shift = key_shift_;
event.key_ctrl = key_ctrl_;
event.key_alt = key_alt_;
event.key_super = key_super_;
break;
case SDL_MOUSEMOTION:
event.type = WindowEventType::MouseMotion;
event.mouse_x = static_cast<float>(sdl_event.motion.x);
event.mouse_y = static_cast<float>(sdl_event.motion.y);
break;
case SDL_MOUSEBUTTONDOWN:
event.type = WindowEventType::MouseButtonDown;
event.mouse_x = static_cast<float>(sdl_event.button.x);
event.mouse_y = static_cast<float>(sdl_event.button.y);
event.mouse_button = sdl_event.button.button;
break;
case SDL_MOUSEBUTTONUP:
event.type = WindowEventType::MouseButtonUp;
event.mouse_x = static_cast<float>(sdl_event.button.x);
event.mouse_y = static_cast<float>(sdl_event.button.y);
event.mouse_button = sdl_event.button.button;
break;
case SDL_MOUSEWHEEL:
event.type = WindowEventType::MouseWheel;
event.wheel_x = static_cast<float>(sdl_event.wheel.x);
event.wheel_y = static_cast<float>(sdl_event.wheel.y);
break;
case SDL_DROPFILE:
event.type = WindowEventType::DropFile;
if (sdl_event.drop.file) {
event.dropped_file = sdl_event.drop.file;
SDL_free(sdl_event.drop.file);
}
break;
case SDL_WINDOWEVENT:
switch (sdl_event.window.event) {
case SDL_WINDOWEVENT_CLOSE:
event.type = WindowEventType::Close;
active_ = false;
break;
case SDL_WINDOWEVENT_SIZE_CHANGED:
case SDL_WINDOWEVENT_RESIZED:
event.type = WindowEventType::Resize;
event.window_width = sdl_event.window.data1;
event.window_height = sdl_event.window.data2;
is_resizing_ = true;
g_window_is_resizing = true;
break;
case SDL_WINDOWEVENT_MINIMIZED:
event.type = WindowEventType::Minimized;
is_resizing_ = false;
g_window_is_resizing = false;
break;
case SDL_WINDOWEVENT_MAXIMIZED:
event.type = WindowEventType::Maximized;
break;
case SDL_WINDOWEVENT_RESTORED:
event.type = WindowEventType::Restored;
is_resizing_ = false;
g_window_is_resizing = false;
break;
case SDL_WINDOWEVENT_SHOWN:
event.type = WindowEventType::Shown;
is_resizing_ = false;
g_window_is_resizing = false;
break;
case SDL_WINDOWEVENT_HIDDEN:
event.type = WindowEventType::Hidden;
is_resizing_ = false;
g_window_is_resizing = false;
break;
case SDL_WINDOWEVENT_EXPOSED:
event.type = WindowEventType::Exposed;
is_resizing_ = false;
g_window_is_resizing = false;
break;
case SDL_WINDOWEVENT_FOCUS_GAINED:
event.type = WindowEventType::FocusGained;
break;
case SDL_WINDOWEVENT_FOCUS_LOST:
event.type = WindowEventType::FocusLost;
break;
}
break;
}
return event;
}
void SDL2WindowBackend::UpdateModifierState() {
SDL_Keymod mod = SDL_GetModState();
key_shift_ = (mod & KMOD_SHIFT) != 0;
key_ctrl_ = (mod & KMOD_CTRL) != 0;
key_alt_ = (mod & KMOD_ALT) != 0;
key_super_ = (mod & KMOD_GUI) != 0;
}
WindowStatus SDL2WindowBackend::GetStatus() const {
WindowStatus status;
status.is_active = active_;
status.is_resizing = is_resizing_;
if (window_) {
Uint32 flags = SDL_GetWindowFlags(window_.get());
status.is_minimized = (flags & SDL_WINDOW_MINIMIZED) != 0;
status.is_maximized = (flags & SDL_WINDOW_MAXIMIZED) != 0;
status.is_fullscreen =
(flags & (SDL_WINDOW_FULLSCREEN | SDL_WINDOW_FULLSCREEN_DESKTOP)) != 0;
status.is_focused = (flags & SDL_WINDOW_INPUT_FOCUS) != 0;
SDL_GetWindowSize(window_.get(), &status.width, &status.height);
}
return status;
}
void SDL2WindowBackend::GetSize(int* width, int* height) const {
if (window_) {
SDL_GetWindowSize(window_.get(), width, height);
} else {
if (width) *width = 0;
if (height) *height = 0;
}
}
void SDL2WindowBackend::SetSize(int width, int height) {
if (window_) {
SDL_SetWindowSize(window_.get(), width, height);
}
}
std::string SDL2WindowBackend::GetTitle() const {
if (window_) {
const char* title = SDL_GetWindowTitle(window_.get());
return title ? title : "";
}
return "";
}
void SDL2WindowBackend::SetTitle(const std::string& title) {
if (window_) {
SDL_SetWindowTitle(window_.get(), title.c_str());
}
}
bool SDL2WindowBackend::InitializeRenderer(gfx::IRenderer* renderer) {
if (!window_ || !renderer) {
return false;
}
if (renderer->GetBackendRenderer()) {
// Already initialized
return true;
}
return renderer->Initialize(window_.get());
}
absl::Status SDL2WindowBackend::InitializeImGui(gfx::IRenderer* renderer) {
if (imgui_initialized_) {
return absl::OkStatus();
}
if (!renderer) {
return absl::InvalidArgumentError("Renderer is null");
}
IMGUI_CHECKVERSION();
ImGui::CreateContext();
ImGuiIO& io = ImGui::GetIO();
io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard;
io.ConfigFlags |= ImGuiConfigFlags_DockingEnable;
// Initialize ImGui backends
SDL_Renderer* sdl_renderer =
static_cast<SDL_Renderer*>(renderer->GetBackendRenderer());
if (!sdl_renderer) {
return absl::InternalError("Failed to get SDL renderer from IRenderer");
}
if (!ImGui_ImplSDL2_InitForSDLRenderer(window_.get(), sdl_renderer)) {
return absl::InternalError("ImGui_ImplSDL2_InitForSDLRenderer failed");
}
if (!ImGui_ImplSDLRenderer2_Init(sdl_renderer)) {
ImGui_ImplSDL2_Shutdown();
return absl::InternalError("ImGui_ImplSDLRenderer2_Init failed");
}
// Load fonts
RETURN_IF_ERROR(LoadPackageFonts());
// Apply default style
gui::ColorsYaze();
imgui_initialized_ = true;
LOG_INFO("SDL2WindowBackend", "ImGui initialized successfully");
return absl::OkStatus();
}
void SDL2WindowBackend::ShutdownImGui() {
if (!imgui_initialized_) {
return;
}
LOG_INFO("SDL2WindowBackend", "Shutting down ImGui implementations...");
ImGui_ImplSDLRenderer2_Shutdown();
ImGui_ImplSDL2_Shutdown();
LOG_INFO("SDL2WindowBackend", "Destroying ImGui context...");
ImGui::DestroyContext();
imgui_initialized_ = false;
}
void SDL2WindowBackend::NewImGuiFrame() {
if (!imgui_initialized_) {
return;
}
ImGui_ImplSDLRenderer2_NewFrame();
ImGui_ImplSDL2_NewFrame();
}
// Define the global variable for backward compatibility
bool g_window_is_resizing = false;
} // namespace platform
} // namespace yaze

View File

@@ -0,0 +1,91 @@
// sdl2_window_backend.h - SDL2 Window Backend Implementation
#ifndef YAZE_APP_PLATFORM_SDL2_WINDOW_BACKEND_H_
#define YAZE_APP_PLATFORM_SDL2_WINDOW_BACKEND_H_
#include "app/platform/sdl_compat.h"
#include <memory>
#include <string>
#include "absl/status/status.h"
#include "app/platform/iwindow.h"
#include "util/sdl_deleter.h"
namespace yaze {
namespace platform {
/**
* @brief SDL2 implementation of the window backend interface
*
* Wraps SDL2 window management, event handling, and ImGui integration
* for the main YAZE application window.
*/
class SDL2WindowBackend : public IWindowBackend {
public:
SDL2WindowBackend() = default;
~SDL2WindowBackend() override;
// =========================================================================
// IWindowBackend Implementation
// =========================================================================
absl::Status Initialize(const WindowConfig& config) override;
absl::Status Shutdown() override;
bool IsInitialized() const override { return initialized_; }
bool PollEvent(WindowEvent& out_event) override;
void ProcessNativeEvent(void* native_event) override;
WindowStatus GetStatus() const override;
bool IsActive() const override { return active_; }
void SetActive(bool active) override { active_ = active; }
void GetSize(int* width, int* height) const override;
void SetSize(int width, int height) override;
std::string GetTitle() const override;
void SetTitle(const std::string& title) override;
bool InitializeRenderer(gfx::IRenderer* renderer) override;
SDL_Window* GetNativeWindow() override { return window_.get(); }
absl::Status InitializeImGui(gfx::IRenderer* renderer) override;
void ShutdownImGui() override;
void NewImGuiFrame() override;
uint32_t GetAudioDevice() const override { return audio_device_; }
std::shared_ptr<int16_t> GetAudioBuffer() const override {
return audio_buffer_;
}
std::string GetBackendName() const override { return "SDL2"; }
int GetSDLVersion() const override { return 2; }
private:
// Convert SDL2 event to platform-agnostic WindowEvent
WindowEvent ConvertSDL2Event(const SDL_Event& sdl_event);
// Update modifier key state from SDL
void UpdateModifierState();
std::unique_ptr<SDL_Window, util::SDL_Deleter> window_;
bool initialized_ = false;
bool active_ = true;
bool is_resizing_ = false;
bool imgui_initialized_ = false;
// Modifier key state
bool key_shift_ = false;
bool key_ctrl_ = false;
bool key_alt_ = false;
bool key_super_ = false;
// Legacy audio support
SDL_AudioDeviceID audio_device_ = 0;
std::shared_ptr<int16_t> audio_buffer_;
};
} // namespace platform
} // namespace yaze
#endif // YAZE_APP_PLATFORM_SDL2_WINDOW_BACKEND_H_

View File

@@ -0,0 +1,456 @@
// sdl3_window_backend.cc - SDL3 Window Backend Implementation
// Only compile SDL3 backend when YAZE_USE_SDL3 is defined
#ifdef YAZE_USE_SDL3
#include "app/platform/sdl3_window_backend.h"
#include <SDL3/SDL.h>
#include "absl/status/status.h"
#include "absl/strings/str_format.h"
#include "app/gfx/resource/arena.h"
#include "app/gui/core/style.h"
#include "app/platform/font_loader.h"
#include "imgui/backends/imgui_impl_sdl3.h"
#include "imgui/backends/imgui_impl_sdlrenderer3.h"
#include "imgui/imgui.h"
#include "util/log.h"
namespace yaze {
namespace platform {
// Global flag for window resize state (used by emulator to pause)
extern bool g_window_is_resizing;
SDL3WindowBackend::~SDL3WindowBackend() {
if (initialized_) {
Shutdown();
}
}
absl::Status SDL3WindowBackend::Initialize(const WindowConfig& config) {
if (initialized_) {
LOG_WARN("SDL3WindowBackend", "Already initialized, shutting down first");
RETURN_IF_ERROR(Shutdown());
}
// Initialize SDL3 subsystems
// Note: SDL3 removed SDL_INIT_TIMER (timer is always available)
if (!SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_EVENTS)) {
return absl::InternalError(
absl::StrFormat("SDL_Init failed: %s", SDL_GetError()));
}
// Determine window size
int screen_width = config.width;
int screen_height = config.height;
if (screen_width == 0 || screen_height == 0) {
// Auto-detect from display
// SDL3 uses SDL_GetPrimaryDisplay() and SDL_GetCurrentDisplayMode()
SDL_DisplayID display_id = SDL_GetPrimaryDisplay();
const SDL_DisplayMode* mode = SDL_GetCurrentDisplayMode(display_id);
if (mode) {
screen_width = static_cast<int>(mode->w * config.display_scale);
screen_height = static_cast<int>(mode->h * config.display_scale);
} else {
// Fallback to reasonable defaults
screen_width = 1280;
screen_height = 720;
LOG_WARN("SDL3WindowBackend",
"Failed to get display mode, using defaults: %dx%d",
screen_width, screen_height);
}
}
// Build window flags
// Note: SDL3 changed some flag names
SDL_WindowFlags flags = 0;
if (config.resizable) {
flags |= SDL_WINDOW_RESIZABLE;
}
if (config.maximized) {
flags |= SDL_WINDOW_MAXIMIZED;
}
if (config.fullscreen) {
flags |= SDL_WINDOW_FULLSCREEN;
}
if (config.high_dpi) {
flags |= SDL_WINDOW_HIGH_PIXEL_DENSITY;
}
// Create window
// Note: SDL3 uses SDL_CreateWindow with different signature
SDL_Window* raw_window =
SDL_CreateWindow(config.title.c_str(), screen_width, screen_height, flags);
if (!raw_window) {
SDL_Quit();
return absl::InternalError(
absl::StrFormat("SDL_CreateWindow failed: %s", SDL_GetError()));
}
window_ = std::unique_ptr<SDL_Window, SDL3WindowDeleter>(raw_window);
// Allocate legacy audio buffer for backwards compatibility
const int audio_frequency = 48000;
const size_t buffer_size = (audio_frequency / 50) * 2; // Stereo PAL
audio_buffer_ = std::shared_ptr<int16_t>(new int16_t[buffer_size],
std::default_delete<int16_t[]>());
LOG_INFO("SDL3WindowBackend",
"Initialized: %dx%d, audio buffer: %zu samples", screen_width,
screen_height, buffer_size);
initialized_ = true;
active_ = true;
return absl::OkStatus();
}
absl::Status SDL3WindowBackend::Shutdown() {
if (!initialized_) {
return absl::OkStatus();
}
// Shutdown ImGui if initialized
if (imgui_initialized_) {
ShutdownImGui();
}
// Shutdown graphics arena while renderer is still valid
LOG_INFO("SDL3WindowBackend", "Shutting down graphics arena...");
gfx::Arena::Get().Shutdown();
// Destroy window
if (window_) {
LOG_INFO("SDL3WindowBackend", "Destroying window...");
window_.reset();
}
// Quit SDL
LOG_INFO("SDL3WindowBackend", "Shutting down SDL...");
SDL_Quit();
initialized_ = false;
LOG_INFO("SDL3WindowBackend", "Shutdown complete");
return absl::OkStatus();
}
bool SDL3WindowBackend::PollEvent(WindowEvent& out_event) {
SDL_Event sdl_event;
if (SDL_PollEvent(&sdl_event)) {
// Let ImGui process the event first
if (imgui_initialized_) {
ImGui_ImplSDL3_ProcessEvent(&sdl_event);
}
// Convert to platform-agnostic event
out_event = ConvertSDL3Event(sdl_event);
return true;
}
return false;
}
void SDL3WindowBackend::ProcessNativeEvent(void* native_event) {
if (native_event && imgui_initialized_) {
ImGui_ImplSDL3_ProcessEvent(static_cast<SDL_Event*>(native_event));
}
}
WindowEvent SDL3WindowBackend::ConvertSDL3Event(const SDL_Event& sdl_event) {
WindowEvent event;
event.type = WindowEventType::None;
switch (sdl_event.type) {
// =========================================================================
// Application Events
// =========================================================================
case SDL_EVENT_QUIT:
event.type = WindowEventType::Quit;
active_ = false;
break;
// =========================================================================
// Keyboard Events
// Note: SDL3 uses event.key.key instead of event.key.keysym.sym
// =========================================================================
case SDL_EVENT_KEY_DOWN:
event.type = WindowEventType::KeyDown;
event.key_code = sdl_event.key.key;
event.scan_code = sdl_event.key.scancode;
UpdateModifierState();
event.key_shift = key_shift_;
event.key_ctrl = key_ctrl_;
event.key_alt = key_alt_;
event.key_super = key_super_;
break;
case SDL_EVENT_KEY_UP:
event.type = WindowEventType::KeyUp;
event.key_code = sdl_event.key.key;
event.scan_code = sdl_event.key.scancode;
UpdateModifierState();
event.key_shift = key_shift_;
event.key_ctrl = key_ctrl_;
event.key_alt = key_alt_;
event.key_super = key_super_;
break;
// =========================================================================
// Mouse Events
// Note: SDL3 uses float coordinates
// =========================================================================
case SDL_EVENT_MOUSE_MOTION:
event.type = WindowEventType::MouseMotion;
event.mouse_x = sdl_event.motion.x;
event.mouse_y = sdl_event.motion.y;
break;
case SDL_EVENT_MOUSE_BUTTON_DOWN:
event.type = WindowEventType::MouseButtonDown;
event.mouse_x = sdl_event.button.x;
event.mouse_y = sdl_event.button.y;
event.mouse_button = sdl_event.button.button;
break;
case SDL_EVENT_MOUSE_BUTTON_UP:
event.type = WindowEventType::MouseButtonUp;
event.mouse_x = sdl_event.button.x;
event.mouse_y = sdl_event.button.y;
event.mouse_button = sdl_event.button.button;
break;
case SDL_EVENT_MOUSE_WHEEL:
event.type = WindowEventType::MouseWheel;
event.wheel_x = sdl_event.wheel.x;
event.wheel_y = sdl_event.wheel.y;
break;
// =========================================================================
// Drop Events
// =========================================================================
case SDL_EVENT_DROP_FILE:
event.type = WindowEventType::DropFile;
if (sdl_event.drop.data) {
event.dropped_file = sdl_event.drop.data;
// Note: SDL3 drop.data is managed by SDL, don't free it
}
break;
// =========================================================================
// Window Events - SDL3 Major Change
// SDL3 no longer uses SDL_WINDOWEVENT with sub-types.
// Each window event type is now a top-level event.
// =========================================================================
case SDL_EVENT_WINDOW_CLOSE_REQUESTED:
event.type = WindowEventType::Close;
active_ = false;
break;
case SDL_EVENT_WINDOW_RESIZED:
event.type = WindowEventType::Resize;
event.window_width = sdl_event.window.data1;
event.window_height = sdl_event.window.data2;
is_resizing_ = true;
g_window_is_resizing = true;
break;
case SDL_EVENT_WINDOW_PIXEL_SIZE_CHANGED:
// This is the SDL3 equivalent of SDL_WINDOWEVENT_SIZE_CHANGED
event.type = WindowEventType::Resize;
event.window_width = sdl_event.window.data1;
event.window_height = sdl_event.window.data2;
is_resizing_ = true;
g_window_is_resizing = true;
break;
case SDL_EVENT_WINDOW_MINIMIZED:
event.type = WindowEventType::Minimized;
is_resizing_ = false;
g_window_is_resizing = false;
break;
case SDL_EVENT_WINDOW_MAXIMIZED:
event.type = WindowEventType::Maximized;
break;
case SDL_EVENT_WINDOW_RESTORED:
event.type = WindowEventType::Restored;
is_resizing_ = false;
g_window_is_resizing = false;
break;
case SDL_EVENT_WINDOW_SHOWN:
event.type = WindowEventType::Shown;
is_resizing_ = false;
g_window_is_resizing = false;
break;
case SDL_EVENT_WINDOW_HIDDEN:
event.type = WindowEventType::Hidden;
is_resizing_ = false;
g_window_is_resizing = false;
break;
case SDL_EVENT_WINDOW_EXPOSED:
event.type = WindowEventType::Exposed;
is_resizing_ = false;
g_window_is_resizing = false;
break;
case SDL_EVENT_WINDOW_FOCUS_GAINED:
event.type = WindowEventType::FocusGained;
break;
case SDL_EVENT_WINDOW_FOCUS_LOST:
event.type = WindowEventType::FocusLost;
break;
}
return event;
}
void SDL3WindowBackend::UpdateModifierState() {
// SDL3 uses SDL_GetModState which returns SDL_Keymod
SDL_Keymod mod = SDL_GetModState();
key_shift_ = (mod & SDL_KMOD_SHIFT) != 0;
key_ctrl_ = (mod & SDL_KMOD_CTRL) != 0;
key_alt_ = (mod & SDL_KMOD_ALT) != 0;
key_super_ = (mod & SDL_KMOD_GUI) != 0;
}
WindowStatus SDL3WindowBackend::GetStatus() const {
WindowStatus status;
status.is_active = active_;
status.is_resizing = is_resizing_;
if (window_) {
SDL_WindowFlags flags = SDL_GetWindowFlags(window_.get());
status.is_minimized = (flags & SDL_WINDOW_MINIMIZED) != 0;
status.is_maximized = (flags & SDL_WINDOW_MAXIMIZED) != 0;
status.is_fullscreen = (flags & SDL_WINDOW_FULLSCREEN) != 0;
status.is_focused = (flags & SDL_WINDOW_INPUT_FOCUS) != 0;
SDL_GetWindowSize(window_.get(), &status.width, &status.height);
}
return status;
}
void SDL3WindowBackend::GetSize(int* width, int* height) const {
if (window_) {
SDL_GetWindowSize(window_.get(), width, height);
} else {
if (width) *width = 0;
if (height) *height = 0;
}
}
void SDL3WindowBackend::SetSize(int width, int height) {
if (window_) {
SDL_SetWindowSize(window_.get(), width, height);
}
}
std::string SDL3WindowBackend::GetTitle() const {
if (window_) {
const char* title = SDL_GetWindowTitle(window_.get());
return title ? title : "";
}
return "";
}
void SDL3WindowBackend::SetTitle(const std::string& title) {
if (window_) {
SDL_SetWindowTitle(window_.get(), title.c_str());
}
}
bool SDL3WindowBackend::InitializeRenderer(gfx::IRenderer* renderer) {
if (!window_ || !renderer) {
return false;
}
if (renderer->GetBackendRenderer()) {
// Already initialized
return true;
}
return renderer->Initialize(window_.get());
}
absl::Status SDL3WindowBackend::InitializeImGui(gfx::IRenderer* renderer) {
if (imgui_initialized_) {
return absl::OkStatus();
}
if (!renderer) {
return absl::InvalidArgumentError("Renderer is null");
}
IMGUI_CHECKVERSION();
ImGui::CreateContext();
ImGuiIO& io = ImGui::GetIO();
io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard;
io.ConfigFlags |= ImGuiConfigFlags_DockingEnable;
// Initialize ImGui backends for SDL3
SDL_Renderer* sdl_renderer =
static_cast<SDL_Renderer*>(renderer->GetBackendRenderer());
if (!sdl_renderer) {
return absl::InternalError("Failed to get SDL renderer from IRenderer");
}
// Note: SDL3 uses different ImGui backend functions
if (!ImGui_ImplSDL3_InitForSDLRenderer(window_.get(), sdl_renderer)) {
return absl::InternalError("ImGui_ImplSDL3_InitForSDLRenderer failed");
}
if (!ImGui_ImplSDLRenderer3_Init(sdl_renderer)) {
ImGui_ImplSDL3_Shutdown();
return absl::InternalError("ImGui_ImplSDLRenderer3_Init failed");
}
// Load fonts
RETURN_IF_ERROR(LoadPackageFonts());
// Apply default style
gui::ColorsYaze();
imgui_initialized_ = true;
LOG_INFO("SDL3WindowBackend", "ImGui initialized successfully");
return absl::OkStatus();
}
void SDL3WindowBackend::ShutdownImGui() {
if (!imgui_initialized_) {
return;
}
LOG_INFO("SDL3WindowBackend", "Shutting down ImGui implementations...");
ImGui_ImplSDLRenderer3_Shutdown();
ImGui_ImplSDL3_Shutdown();
LOG_INFO("SDL3WindowBackend", "Destroying ImGui context...");
ImGui::DestroyContext();
imgui_initialized_ = false;
}
void SDL3WindowBackend::NewImGuiFrame() {
if (!imgui_initialized_) {
return;
}
ImGui_ImplSDLRenderer3_NewFrame();
ImGui_ImplSDL3_NewFrame();
}
} // namespace platform
} // namespace yaze
#endif // YAZE_USE_SDL3

View File

@@ -0,0 +1,103 @@
// sdl3_window_backend.h - SDL3 Window Backend Implementation
#ifndef YAZE_APP_PLATFORM_SDL3_WINDOW_BACKEND_H_
#define YAZE_APP_PLATFORM_SDL3_WINDOW_BACKEND_H_
// Only compile SDL3 backend when YAZE_USE_SDL3 is defined
#ifdef YAZE_USE_SDL3
#include <SDL3/SDL.h>
#include <memory>
#include <string>
#include "absl/status/status.h"
#include "app/platform/iwindow.h"
namespace yaze {
namespace platform {
// Forward declaration for unique_ptr custom deleter
struct SDL3WindowDeleter {
void operator()(SDL_Window* p) const {
if (p) SDL_DestroyWindow(p);
}
};
/**
* @brief SDL3 implementation of the window backend interface
*
* Handles the significant event handling changes in SDL3:
* - Individual window events instead of SDL_WINDOWEVENT
* - SDL_EVENT_* naming convention
* - event.key.key instead of event.key.keysym.sym
* - bool* keyboard state instead of Uint8*
*/
class SDL3WindowBackend : public IWindowBackend {
public:
SDL3WindowBackend() = default;
~SDL3WindowBackend() override;
// =========================================================================
// IWindowBackend Implementation
// =========================================================================
absl::Status Initialize(const WindowConfig& config) override;
absl::Status Shutdown() override;
bool IsInitialized() const override { return initialized_; }
bool PollEvent(WindowEvent& out_event) override;
void ProcessNativeEvent(void* native_event) override;
WindowStatus GetStatus() const override;
bool IsActive() const override { return active_; }
void SetActive(bool active) override { active_ = active; }
void GetSize(int* width, int* height) const override;
void SetSize(int width, int height) override;
std::string GetTitle() const override;
void SetTitle(const std::string& title) override;
bool InitializeRenderer(gfx::IRenderer* renderer) override;
SDL_Window* GetNativeWindow() override { return window_.get(); }
absl::Status InitializeImGui(gfx::IRenderer* renderer) override;
void ShutdownImGui() override;
void NewImGuiFrame() override;
uint32_t GetAudioDevice() const override { return 0; } // SDL3 uses streams
std::shared_ptr<int16_t> GetAudioBuffer() const override {
return audio_buffer_;
}
std::string GetBackendName() const override { return "SDL3"; }
int GetSDLVersion() const override { return 3; }
private:
// Convert SDL3 event to platform-agnostic WindowEvent
WindowEvent ConvertSDL3Event(const SDL_Event& sdl_event);
// Update modifier key state from SDL3
void UpdateModifierState();
std::unique_ptr<SDL_Window, SDL3WindowDeleter> window_;
bool initialized_ = false;
bool active_ = true;
bool is_resizing_ = false;
bool imgui_initialized_ = false;
// Modifier key state
bool key_shift_ = false;
bool key_ctrl_ = false;
bool key_alt_ = false;
bool key_super_ = false;
// Legacy audio buffer for compatibility
std::shared_ptr<int16_t> audio_buffer_;
};
} // namespace platform
} // namespace yaze
#endif // YAZE_USE_SDL3
#endif // YAZE_APP_PLATFORM_SDL3_WINDOW_BACKEND_H_

View File

@@ -0,0 +1,510 @@
#ifndef YAZE_APP_PLATFORM_SDL_COMPAT_H_
#define YAZE_APP_PLATFORM_SDL_COMPAT_H_
/**
* @file sdl_compat.h
* @brief SDL2/SDL3 compatibility layer
*
* This header provides cross-version compatibility between SDL2 and SDL3.
* It defines type aliases, macros, and wrapper functions that allow
* application code to work with both versions.
*/
#ifdef YAZE_USE_SDL3
#include <SDL3/SDL.h>
#else
#include <SDL.h>
#endif
namespace yaze {
namespace platform {
// ============================================================================
// Type Aliases
// ============================================================================
#ifdef YAZE_USE_SDL3
// SDL3 uses bool* for keyboard state
using KeyboardState = const bool*;
#else
// SDL2 uses Uint8* for keyboard state
using KeyboardState = const Uint8*;
#endif
// ============================================================================
// Event Type Constants
// ============================================================================
#ifdef YAZE_USE_SDL3
constexpr auto kEventKeyDown = SDL_EVENT_KEY_DOWN;
constexpr auto kEventKeyUp = SDL_EVENT_KEY_UP;
constexpr auto kEventMouseMotion = SDL_EVENT_MOUSE_MOTION;
constexpr auto kEventMouseButtonDown = SDL_EVENT_MOUSE_BUTTON_DOWN;
constexpr auto kEventMouseButtonUp = SDL_EVENT_MOUSE_BUTTON_UP;
constexpr auto kEventMouseWheel = SDL_EVENT_MOUSE_WHEEL;
constexpr auto kEventQuit = SDL_EVENT_QUIT;
constexpr auto kEventDropFile = SDL_EVENT_DROP_FILE;
constexpr auto kEventWindowCloseRequested = SDL_EVENT_WINDOW_CLOSE_REQUESTED;
constexpr auto kEventWindowResized = SDL_EVENT_WINDOW_RESIZED;
constexpr auto kEventGamepadAdded = SDL_EVENT_GAMEPAD_ADDED;
constexpr auto kEventGamepadRemoved = SDL_EVENT_GAMEPAD_REMOVED;
#else
constexpr auto kEventKeyDown = SDL_KEYDOWN;
constexpr auto kEventKeyUp = SDL_KEYUP;
constexpr auto kEventMouseMotion = SDL_MOUSEMOTION;
constexpr auto kEventMouseButtonDown = SDL_MOUSEBUTTONDOWN;
constexpr auto kEventMouseButtonUp = SDL_MOUSEBUTTONUP;
constexpr auto kEventMouseWheel = SDL_MOUSEWHEEL;
constexpr auto kEventQuit = SDL_QUIT;
constexpr auto kEventDropFile = SDL_DROPFILE;
// SDL2 uses SDL_WINDOWEVENT with sub-types, not individual events
// These are handled specially in window code
constexpr auto kEventWindowEvent = SDL_WINDOWEVENT;
constexpr auto kEventControllerDeviceAdded = SDL_CONTROLLERDEVICEADDED;
constexpr auto kEventControllerDeviceRemoved = SDL_CONTROLLERDEVICEREMOVED;
#endif
// ============================================================================
// Keyboard Helpers
// ============================================================================
/**
* @brief Get keyboard state from SDL event
* @param event The SDL event
* @return The keycode from the event
*/
inline SDL_Keycode GetKeyFromEvent(const SDL_Event& event) {
#ifdef YAZE_USE_SDL3
return event.key.key;
#else
return event.key.keysym.sym;
#endif
}
/**
* @brief Check if a key is pressed using the keyboard state
* @param state The keyboard state from SDL_GetKeyboardState
* @param scancode The scancode to check
* @return True if the key is pressed
*/
inline bool IsKeyPressed(KeyboardState state, SDL_Scancode scancode) {
#ifdef YAZE_USE_SDL3
// SDL3 returns bool*
return state[scancode];
#else
// SDL2 returns Uint8*, non-zero means pressed
return state[scancode] != 0;
#endif
}
// ============================================================================
// Gamepad/Controller Helpers
// ============================================================================
#ifdef YAZE_USE_SDL3
// SDL3 uses SDL_Gamepad instead of SDL_GameController
using GamepadHandle = SDL_Gamepad*;
inline GamepadHandle OpenGamepad(int index) {
SDL_JoystickID* joysticks = SDL_GetGamepads(nullptr);
if (joysticks && index < 4) {
SDL_JoystickID id = joysticks[index];
SDL_free(joysticks);
return SDL_OpenGamepad(id);
}
if (joysticks) SDL_free(joysticks);
return nullptr;
}
inline void CloseGamepad(GamepadHandle gamepad) {
if (gamepad) SDL_CloseGamepad(gamepad);
}
inline bool GetGamepadButton(GamepadHandle gamepad, SDL_GamepadButton button) {
return SDL_GetGamepadButton(gamepad, button);
}
inline int16_t GetGamepadAxis(GamepadHandle gamepad, SDL_GamepadAxis axis) {
return SDL_GetGamepadAxis(gamepad, axis);
}
inline bool IsGamepadConnected(int index) {
int count = 0;
SDL_JoystickID* joysticks = SDL_GetGamepads(&count);
if (joysticks) {
SDL_free(joysticks);
}
return index < count;
}
#else
// SDL2 uses SDL_GameController
using GamepadHandle = SDL_GameController*;
inline GamepadHandle OpenGamepad(int index) {
if (SDL_IsGameController(index)) {
return SDL_GameControllerOpen(index);
}
return nullptr;
}
inline void CloseGamepad(GamepadHandle gamepad) {
if (gamepad) SDL_GameControllerClose(gamepad);
}
inline bool GetGamepadButton(GamepadHandle gamepad,
SDL_GameControllerButton button) {
return SDL_GameControllerGetButton(gamepad, button) != 0;
}
inline int16_t GetGamepadAxis(GamepadHandle gamepad,
SDL_GameControllerAxis axis) {
return SDL_GameControllerGetAxis(gamepad, axis);
}
inline bool IsGamepadConnected(int index) {
return SDL_IsGameController(index);
}
#endif
// ============================================================================
// Button/Axis Type Aliases
// ============================================================================
#ifdef YAZE_USE_SDL3
using GamepadButton = SDL_GamepadButton;
using GamepadAxis = SDL_GamepadAxis;
constexpr auto kGamepadButtonA = SDL_GAMEPAD_BUTTON_SOUTH;
constexpr auto kGamepadButtonB = SDL_GAMEPAD_BUTTON_EAST;
constexpr auto kGamepadButtonX = SDL_GAMEPAD_BUTTON_WEST;
constexpr auto kGamepadButtonY = SDL_GAMEPAD_BUTTON_NORTH;
constexpr auto kGamepadButtonBack = SDL_GAMEPAD_BUTTON_BACK;
constexpr auto kGamepadButtonStart = SDL_GAMEPAD_BUTTON_START;
constexpr auto kGamepadButtonLeftShoulder = SDL_GAMEPAD_BUTTON_LEFT_SHOULDER;
constexpr auto kGamepadButtonRightShoulder = SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER;
constexpr auto kGamepadButtonDpadUp = SDL_GAMEPAD_BUTTON_DPAD_UP;
constexpr auto kGamepadButtonDpadDown = SDL_GAMEPAD_BUTTON_DPAD_DOWN;
constexpr auto kGamepadButtonDpadLeft = SDL_GAMEPAD_BUTTON_DPAD_LEFT;
constexpr auto kGamepadButtonDpadRight = SDL_GAMEPAD_BUTTON_DPAD_RIGHT;
constexpr auto kGamepadAxisLeftX = SDL_GAMEPAD_AXIS_LEFTX;
constexpr auto kGamepadAxisLeftY = SDL_GAMEPAD_AXIS_LEFTY;
#else
using GamepadButton = SDL_GameControllerButton;
using GamepadAxis = SDL_GameControllerAxis;
constexpr auto kGamepadButtonA = SDL_CONTROLLER_BUTTON_A;
constexpr auto kGamepadButtonB = SDL_CONTROLLER_BUTTON_B;
constexpr auto kGamepadButtonX = SDL_CONTROLLER_BUTTON_X;
constexpr auto kGamepadButtonY = SDL_CONTROLLER_BUTTON_Y;
constexpr auto kGamepadButtonBack = SDL_CONTROLLER_BUTTON_BACK;
constexpr auto kGamepadButtonStart = SDL_CONTROLLER_BUTTON_START;
constexpr auto kGamepadButtonLeftShoulder = SDL_CONTROLLER_BUTTON_LEFTSHOULDER;
constexpr auto kGamepadButtonRightShoulder = SDL_CONTROLLER_BUTTON_RIGHTSHOULDER;
constexpr auto kGamepadButtonDpadUp = SDL_CONTROLLER_BUTTON_DPAD_UP;
constexpr auto kGamepadButtonDpadDown = SDL_CONTROLLER_BUTTON_DPAD_DOWN;
constexpr auto kGamepadButtonDpadLeft = SDL_CONTROLLER_BUTTON_DPAD_LEFT;
constexpr auto kGamepadButtonDpadRight = SDL_CONTROLLER_BUTTON_DPAD_RIGHT;
constexpr auto kGamepadAxisLeftX = SDL_CONTROLLER_AXIS_LEFTX;
constexpr auto kGamepadAxisLeftY = SDL_CONTROLLER_AXIS_LEFTY;
#endif
// ============================================================================
// Renderer Helpers
// ============================================================================
/**
* @brief Create a renderer with default settings.
*
* SDL2: SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED)
* SDL3: SDL_CreateRenderer(window, nullptr)
*/
inline SDL_Renderer* CreateRenderer(SDL_Window* window) {
#ifdef YAZE_USE_SDL3
return SDL_CreateRenderer(window, nullptr);
#else
return SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED);
#endif
}
/**
* @brief Set vertical sync for the renderer.
*
* SDL2: VSync is set at renderer creation time via flags
* SDL3: SDL_SetRenderVSync(renderer, interval)
*/
inline void SetRenderVSync(SDL_Renderer* renderer, int interval) {
#ifdef YAZE_USE_SDL3
SDL_SetRenderVSync(renderer, interval);
#else
// SDL2 sets vsync at creation time, this is a no-op
(void)renderer;
(void)interval;
#endif
}
/**
* @brief Render a texture to the current render target.
*
* SDL2: SDL_RenderCopy(renderer, texture, srcrect, dstrect)
* SDL3: SDL_RenderTexture(renderer, texture, srcrect, dstrect)
*
* Note: This version handles the int to float conversion for SDL3.
*/
inline bool RenderTexture(SDL_Renderer* renderer, SDL_Texture* texture,
const SDL_Rect* srcrect, const SDL_Rect* dstrect) {
#ifdef YAZE_USE_SDL3
SDL_FRect src_frect, dst_frect;
SDL_FRect* src_ptr = nullptr;
SDL_FRect* dst_ptr = nullptr;
if (srcrect) {
src_frect.x = static_cast<float>(srcrect->x);
src_frect.y = static_cast<float>(srcrect->y);
src_frect.w = static_cast<float>(srcrect->w);
src_frect.h = static_cast<float>(srcrect->h);
src_ptr = &src_frect;
}
if (dstrect) {
dst_frect.x = static_cast<float>(dstrect->x);
dst_frect.y = static_cast<float>(dstrect->y);
dst_frect.w = static_cast<float>(dstrect->w);
dst_frect.h = static_cast<float>(dstrect->h);
dst_ptr = &dst_frect;
}
return SDL_RenderTexture(renderer, texture, src_ptr, dst_ptr);
#else
return SDL_RenderCopy(renderer, texture, srcrect, dstrect) == 0;
#endif
}
// ============================================================================
// Surface Helpers
// ============================================================================
/**
* @brief Free/destroy a surface.
*
* SDL2: SDL_FreeSurface(surface)
* SDL3: SDL_DestroySurface(surface)
*/
inline void FreeSurface(SDL_Surface* surface) {
if (!surface) return;
#ifdef YAZE_USE_SDL3
SDL_DestroySurface(surface);
#else
SDL_FreeSurface(surface);
#endif
}
/**
* @brief Convert a surface to a specific pixel format.
*
* SDL2: SDL_ConvertSurfaceFormat(surface, format, flags)
* SDL3: SDL_ConvertSurface(surface, format)
*/
inline SDL_Surface* ConvertSurfaceFormat(SDL_Surface* surface, uint32_t format,
uint32_t flags = 0) {
if (!surface) return nullptr;
#ifdef YAZE_USE_SDL3
(void)flags; // SDL3 removed flags parameter
return SDL_ConvertSurface(surface, format);
#else
return SDL_ConvertSurfaceFormat(surface, format, flags);
#endif
}
/**
* @brief Get bits per pixel from a surface.
*
* SDL2: surface->format->BitsPerPixel
* SDL3: SDL_GetPixelFormatDetails(surface->format)->bits_per_pixel
*/
inline int GetSurfaceBitsPerPixel(SDL_Surface* surface) {
if (!surface) return 0;
#ifdef YAZE_USE_SDL3
const SDL_PixelFormatDetails* details =
SDL_GetPixelFormatDetails(surface->format);
return details ? details->bits_per_pixel : 0;
#else
return surface->format ? surface->format->BitsPerPixel : 0;
#endif
}
/**
* @brief Get bytes per pixel from a surface.
*
* SDL2: surface->format->BytesPerPixel
* SDL3: SDL_GetPixelFormatDetails(surface->format)->bytes_per_pixel
*/
inline int GetSurfaceBytesPerPixel(SDL_Surface* surface) {
if (!surface) return 0;
#ifdef YAZE_USE_SDL3
const SDL_PixelFormatDetails* details =
SDL_GetPixelFormatDetails(surface->format);
return details ? details->bytes_per_pixel : 0;
#else
return surface->format ? surface->format->BytesPerPixel : 0;
#endif
}
// ============================================================================
// Window Event Compatibility Macros
// These macros allow code to handle window events consistently across SDL2/SDL3
// ============================================================================
#ifdef YAZE_USE_SDL3
// SDL3 has individual window events at the top level
#define YAZE_SDL_QUIT SDL_EVENT_QUIT
#define YAZE_SDL_WINDOWEVENT 0 // Placeholder - SDL3 has no combined event
// SDL3 window events are individual event types
#define YAZE_SDL_WINDOW_CLOSE SDL_EVENT_WINDOW_CLOSE_REQUESTED
#define YAZE_SDL_WINDOW_RESIZED SDL_EVENT_WINDOW_RESIZED
#define YAZE_SDL_WINDOW_SIZE_CHANGED SDL_EVENT_WINDOW_PIXEL_SIZE_CHANGED
#define YAZE_SDL_WINDOW_MINIMIZED SDL_EVENT_WINDOW_MINIMIZED
#define YAZE_SDL_WINDOW_MAXIMIZED SDL_EVENT_WINDOW_MAXIMIZED
#define YAZE_SDL_WINDOW_RESTORED SDL_EVENT_WINDOW_RESTORED
#define YAZE_SDL_WINDOW_SHOWN SDL_EVENT_WINDOW_SHOWN
#define YAZE_SDL_WINDOW_HIDDEN SDL_EVENT_WINDOW_HIDDEN
#define YAZE_SDL_WINDOW_EXPOSED SDL_EVENT_WINDOW_EXPOSED
#define YAZE_SDL_WINDOW_FOCUS_GAINED SDL_EVENT_WINDOW_FOCUS_GAINED
#define YAZE_SDL_WINDOW_FOCUS_LOST SDL_EVENT_WINDOW_FOCUS_LOST
// SDL3 has no nested window events
#define YAZE_SDL_HAS_INDIVIDUAL_WINDOW_EVENTS 1
#else // SDL2
// SDL2 event types
#define YAZE_SDL_QUIT SDL_QUIT
#define YAZE_SDL_WINDOWEVENT SDL_WINDOWEVENT
// SDL2 window events are nested under SDL_WINDOWEVENT
#define YAZE_SDL_WINDOW_CLOSE SDL_WINDOWEVENT_CLOSE
#define YAZE_SDL_WINDOW_RESIZED SDL_WINDOWEVENT_RESIZED
#define YAZE_SDL_WINDOW_SIZE_CHANGED SDL_WINDOWEVENT_SIZE_CHANGED
#define YAZE_SDL_WINDOW_MINIMIZED SDL_WINDOWEVENT_MINIMIZED
#define YAZE_SDL_WINDOW_MAXIMIZED SDL_WINDOWEVENT_MAXIMIZED
#define YAZE_SDL_WINDOW_RESTORED SDL_WINDOWEVENT_RESTORED
#define YAZE_SDL_WINDOW_SHOWN SDL_WINDOWEVENT_SHOWN
#define YAZE_SDL_WINDOW_HIDDEN SDL_WINDOWEVENT_HIDDEN
#define YAZE_SDL_WINDOW_EXPOSED SDL_WINDOWEVENT_EXPOSED
#define YAZE_SDL_WINDOW_FOCUS_GAINED SDL_WINDOWEVENT_FOCUS_GAINED
#define YAZE_SDL_WINDOW_FOCUS_LOST SDL_WINDOWEVENT_FOCUS_LOST
// SDL2 uses nested window events
#define YAZE_SDL_HAS_INDIVIDUAL_WINDOW_EVENTS 0
#endif // YAZE_USE_SDL3
// ============================================================================
// Window Event Helper Functions
// ============================================================================
/**
* @brief Check if an event is a window close event
* Works correctly for both SDL2 (nested) and SDL3 (individual) events
*/
inline bool IsWindowCloseEvent(const SDL_Event& event) {
#ifdef YAZE_USE_SDL3
return event.type == SDL_EVENT_WINDOW_CLOSE_REQUESTED;
#else
return event.type == SDL_WINDOWEVENT &&
event.window.event == SDL_WINDOWEVENT_CLOSE;
#endif
}
/**
* @brief Check if an event is a window resize event
*/
inline bool IsWindowResizeEvent(const SDL_Event& event) {
#ifdef YAZE_USE_SDL3
return event.type == SDL_EVENT_WINDOW_RESIZED ||
event.type == SDL_EVENT_WINDOW_PIXEL_SIZE_CHANGED;
#else
return event.type == SDL_WINDOWEVENT &&
(event.window.event == SDL_WINDOWEVENT_RESIZED ||
event.window.event == SDL_WINDOWEVENT_SIZE_CHANGED);
#endif
}
/**
* @brief Check if an event is a window minimize event
*/
inline bool IsWindowMinimizedEvent(const SDL_Event& event) {
#ifdef YAZE_USE_SDL3
return event.type == SDL_EVENT_WINDOW_MINIMIZED;
#else
return event.type == SDL_WINDOWEVENT &&
event.window.event == SDL_WINDOWEVENT_MINIMIZED;
#endif
}
/**
* @brief Check if an event is a window restore event
*/
inline bool IsWindowRestoredEvent(const SDL_Event& event) {
#ifdef YAZE_USE_SDL3
return event.type == SDL_EVENT_WINDOW_RESTORED;
#else
return event.type == SDL_WINDOWEVENT &&
event.window.event == SDL_WINDOWEVENT_RESTORED;
#endif
}
/**
* @brief Get window width from resize event data
*/
inline int GetWindowEventWidth(const SDL_Event& event) {
return event.window.data1;
}
/**
* @brief Get window height from resize event data
*/
inline int GetWindowEventHeight(const SDL_Event& event) {
return event.window.data2;
}
// ============================================================================
// Initialization Helpers
// ============================================================================
/**
* @brief Check if SDL initialization succeeded.
*
* SDL2: Returns 0 on success
* SDL3: Returns true (non-zero) on success
*/
inline bool InitSucceeded(int result) {
#ifdef YAZE_USE_SDL3
// SDL3 returns bool (non-zero for success)
return result != 0;
#else
// SDL2 returns 0 for success
return result == 0;
#endif
}
/**
* @brief Get recommended init flags.
*
* SDL3 removed SDL_INIT_TIMER (timer is always available).
*/
inline uint32_t GetDefaultInitFlags() {
#ifdef YAZE_USE_SDL3
return SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_EVENTS;
#else
return SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_TIMER | SDL_INIT_EVENTS;
#endif
}
} // namespace platform
} // namespace yaze
#endif // YAZE_APP_PLATFORM_SDL_COMPAT_H_

View File

@@ -1,7 +1,7 @@
#ifndef YAZE_APP_CORE_TIMING_H
#define YAZE_APP_CORE_TIMING_H
#include <SDL.h>
#include "app/platform/sdl_compat.h"
#include <cstdint>

View File

@@ -1,7 +1,7 @@
#ifndef YAZE_CORE_WINDOW_H_
#define YAZE_CORE_WINDOW_H_
#include <SDL.h>
#include "app/platform/sdl_compat.h"
#include <memory>

View File

@@ -0,0 +1,75 @@
// window_backend_factory.cc - Window Backend Factory Implementation
#include "app/platform/iwindow.h"
#include "app/platform/sdl2_window_backend.h"
#include "util/log.h"
#ifdef YAZE_USE_SDL3
#include "app/platform/sdl3_window_backend.h"
#endif
namespace yaze {
namespace platform {
std::unique_ptr<IWindowBackend> WindowBackendFactory::Create(
WindowBackendType type) {
switch (type) {
case WindowBackendType::SDL2:
#ifndef YAZE_USE_SDL3
return std::make_unique<SDL2WindowBackend>();
#else
LOG_WARN("WindowBackendFactory",
"SDL2 backend requested but built with SDL3, using SDL3");
return std::make_unique<SDL3WindowBackend>();
#endif
case WindowBackendType::SDL3:
#ifdef YAZE_USE_SDL3
return std::make_unique<SDL3WindowBackend>();
#else
LOG_WARN("WindowBackendFactory",
"SDL3 backend requested but not available, using SDL2");
return std::make_unique<SDL2WindowBackend>();
#endif
case WindowBackendType::Auto:
default:
return Create(GetDefaultType());
}
}
WindowBackendType WindowBackendFactory::GetDefaultType() {
#ifdef YAZE_USE_SDL3
return WindowBackendType::SDL3;
#else
return WindowBackendType::SDL2;
#endif
}
bool WindowBackendFactory::IsAvailable(WindowBackendType type) {
switch (type) {
case WindowBackendType::SDL2:
#ifdef YAZE_USE_SDL3
return false; // Built with SDL3, SDL2 not available
#else
return true;
#endif
case WindowBackendType::SDL3:
#ifdef YAZE_USE_SDL3
return true;
#else
return false; // SDL3 not built in
#endif
case WindowBackendType::Auto:
return true; // Auto always available
default:
return false;
}
}
} // namespace platform
} // namespace yaze