Implement Portable Multi Platform layer

Implement Various Platforms
This commit is contained in:
ApfelTeeSaft
2025-11-28 14:13:34 +01:00
parent db251e4556
commit dabb85f936
38 changed files with 3961 additions and 26 deletions
+2
View File
@@ -0,0 +1,2 @@
# Auto detect text files and perform LF normalization
* text=auto
+22 -1
View File
@@ -22,6 +22,26 @@ endmacro()
# Add glad
_add_engine_git_module(glad https://github.com/Dav1dde/glad.git 5bf3eda)
# Platform-specific sources
set(PLATFORM_SOURCES "")
if(PLATFORM_WINDOWS OR PLATFORM_LINUX OR PLATFORM_MACOS)
list(APPEND PLATFORM_SOURCES
src/platform/Desktop/DesktopPlatform.cpp
src/platform/Desktop/DesktopGraphicsContext.cpp
)
# Desktop needs GLAD
list(APPEND PLATFORM_SOURCES ${glad_SOURCE_DIR}/src/glad.c)
endif()
# NOTE: Console and mobile platforms need their respective SDKs and toolchains
# These are provided as templates and will compile when the appropriate SDK is available
# Platform factory (always included)
list(APPEND PLATFORM_SOURCES
src/platform/PlatformFactory.cpp
)
# Create library
add_library(WVCore STATIC
src/Logger.cpp
@@ -43,7 +63,8 @@ add_library(WVCore STATIC
src/threading/ThreadPool.cpp
${glad_SOURCE_DIR}/src/glad.c
# Platform abstraction sources
${PLATFORM_SOURCES}
)
#target_precompile_headers(WVCore PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include/wv/wvpch.h)
+18 -1
View File
@@ -1,7 +1,13 @@
#pragma once
#include <wv/platform/InputState.h>
namespace WillowVox
{
// Forward declarations
class IPlatform;
class IGraphicsContext;
extern const char* appWindowName;
extern int appDefaultWindowX;
extern int appDefaultWindowY;
@@ -12,13 +18,24 @@ namespace WillowVox
void Run();
virtual void Start() {}
virtual void Update() {}
virtual void Update(const InputState& input) {}
virtual void Render() {}
static float m_deltaTime;
// Access to platform and graphics context
IPlatform* GetPlatform() const { return m_platform; }
IGraphicsContext* GetGraphicsContext() const { return m_graphicsContext; }
protected:
// Derived apps can access input state
InputState m_inputState;
private:
static float m_lastFrame;
IPlatform* m_platform = nullptr;
IGraphicsContext* m_graphicsContext = nullptr;
};
App* CreateApp();
+196
View File
@@ -0,0 +1,196 @@
#pragma once
/**
* Platform-Agnostic Entry Point
*
* This header provides the correct entry point for each platform.
* Game code should include this instead of defining main() manually.
*
* Usage:
* #include <wv/app/PlatformEntryPoint.h>
*
* WillowVox::App* WillowVox::CreateApp()
* {
* return new MyGameApp();
* }
*/
#include <wv/app/App.h>
#include <wv/platform/PlatformConfig.h>
// Forward declare CreateApp
extern WillowVox::App* WillowVox::CreateApp();
// ============================================================================
// Desktop Platforms (Windows, Linux, macOS)
// ============================================================================
#if defined(PLATFORM_DESKTOP)
int main(int argc, char** argv)
{
auto app = WillowVox::CreateApp();
app->Run();
delete app;
return 0;
}
// ============================================================================
// Android
// ============================================================================
#elif defined(PLATFORM_ANDROID)
#include <android_native_app_glue.h>
void android_main(struct android_app* state)
{
// Set up the android app state
// The platform implementation will handle this
auto app = WillowVox::CreateApp();
// Android apps need to be tied to the android_app state
// This is handled internally by AndroidPlatform
app->Run();
delete app;
}
// ============================================================================
// iOS
// ============================================================================
#elif defined(PLATFORM_IOS)
// iOS uses UIApplicationMain, but we can provide a standard main()
// The actual setup happens in the platform layer
int main(int argc, char** argv)
{
// iOS-specific initialization happens in the platform layer
auto app = WillowVox::CreateApp();
app->Run();
delete app;
return 0;
}
// ============================================================================
// PlayStation 3
// ============================================================================
#elif defined(PLATFORM_PS3)
// PS3 homebrew standard main entry point
int main(int argc, char** argv)
{
auto app = WillowVox::CreateApp();
app->Run();
delete app;
return 0;
}
// ============================================================================
// PlayStation 4
// ============================================================================
#elif defined(PLATFORM_PS4)
// PS4 homebrew main entry point
int main(int argc, char** argv)
{
auto app = WillowVox::CreateApp();
app->Run();
delete app;
return 0;
}
// ============================================================================
// Nintendo Wii
// ============================================================================
#elif defined(PLATFORM_WII)
// Wii homebrew main entry point (devkitPPC)
int main(int argc, char** argv)
{
auto app = WillowVox::CreateApp();
app->Run();
delete app;
return 0;
}
// ============================================================================
// Nintendo GameCube
// ============================================================================
#elif defined(PLATFORM_GAMECUBE)
// GameCube homebrew main entry point (devkitPPC)
int main(int argc, char** argv)
{
auto app = WillowVox::CreateApp();
app->Run();
delete app;
return 0;
}
// ============================================================================
// Nintendo Wii U
// ============================================================================
#elif defined(PLATFORM_WIIU)
// Wii U homebrew main entry point (devkitPPC + wut)
int main(int argc, char** argv)
{
auto app = WillowVox::CreateApp();
app->Run();
delete app;
return 0;
}
// ============================================================================
// Nintendo Switch
// ============================================================================
#elif defined(PLATFORM_SWITCH)
// Switch homebrew main entry point (devkitA64 + libnx)
int main(int argc, char** argv)
{
auto app = WillowVox::CreateApp();
app->Run();
delete app;
return 0;
}
// ============================================================================
// Xbox Series X/S (Dev Mode / GDK)
// ============================================================================
#elif defined(PLATFORM_XBOX_SERIES_DEV)
// Xbox uses a different entry point for UWP/GDK
// This may need adjustment based on the specific Xbox SDK being used
int main(int argc, char** argv)
{
auto app = WillowVox::CreateApp();
app->Run();
delete app;
return 0;
}
// For full GDK, might need:
// int __cdecl main(Platform::Array<Platform::String^>^ args)
// {
// auto app = WillowVox::CreateApp();
// app->Run();
// delete app;
// return 0;
// }
#else
#error "Unsupported platform for entry point!"
#endif
+77
View File
@@ -0,0 +1,77 @@
#pragma once
#include <wv/platform/PlatformConfig.h>
#include <wv/wvpch.h>
namespace WillowVox
{
/**
* Abstract Graphics Context Interface
*
* This interface abstracts the platform-specific graphics initialization,
* window/framebuffer management, and basic rendering operations.
*
* Each platform implements this interface using its native graphics API:
* - Desktop: OpenGL + GLFW
* - Android: OpenGL ES + EGL/ANativeWindow
* - iOS: Metal or OpenGL ES + UIKit
* - PS3: PSGL or GCM
* - PS4: GNM
* - Wii/GC: GX
* - Wii U: GX2
* - Switch: OpenGL or NVN
* - Xbox: DirectX 12
*/
class IGraphicsContext
{
public:
virtual ~IGraphicsContext() = default;
// Initialization and shutdown
virtual bool Initialize(int width, int height, const char* title) = 0;
virtual void Shutdown() = 0;
// Frame management
virtual void BeginFrame() = 0;
virtual void EndFrame() = 0;
virtual void SwapBuffers() = 0;
// Clear operations
virtual void Clear(float r, float g, float b, float a) = 0;
virtual void ClearDepth() = 0;
// Window management
virtual bool ShouldClose() const = 0;
virtual void SetShouldClose(bool shouldClose) = 0;
virtual void GetFramebufferSize(int& width, int& height) const = 0;
virtual void GetWindowSize(int& width, int& height) const = 0;
// VSync
virtual void SetVSync(bool enabled) = 0;
virtual bool IsVSyncEnabled() const = 0;
// Time
virtual float GetTime() const = 0;
// Background color
virtual void SetBackgroundColor(float r, float g, float b, float a) = 0;
// Platform-specific native handles (optional, for advanced use)
virtual void* GetNativeWindowHandle() const { return nullptr; }
virtual void* GetNativeGraphicsHandle() const { return nullptr; }
// Viewport
virtual void SetViewport(int x, int y, int width, int height) = 0;
};
/**
* Graphics Context Factory
*
* Creates the appropriate graphics context for the current platform.
*/
class GraphicsContextFactory
{
public:
static IGraphicsContext* CreateGraphicsContext();
};
}
+126
View File
@@ -0,0 +1,126 @@
#pragma once
#include <wv/platform/PlatformConfig.h>
#include <wv/platform/InputState.h>
#include <wv/platform/IGraphicsContext.h>
namespace WillowVox
{
/**
* Abstract Platform Interface
*
* This interface defines the contract that each platform must implement.
* It handles platform-specific initialization, input polling, events,
* and any OS-level operations.
*
* The platform layer is responsible for:
* - Creating and managing the graphics context
* - Polling and translating input to InputState
* - File system access
* - Platform-specific main loop integration
*/
class IPlatform
{
public:
virtual ~IPlatform() = default;
// Platform lifecycle
virtual bool Initialize() = 0;
virtual void Shutdown() = 0;
// Input
virtual void PollInput(InputState& outInputState) = 0;
virtual void ResetInputFrameState(InputState& inputState) = 0;
// Events (platform may have specific event handling)
virtual void ProcessEvents() = 0;
// Graphics context access
virtual IGraphicsContext* GetGraphicsContext() = 0;
// File system
virtual const char* GetUserDataPath() const = 0;
virtual const char* GetAssetsPath() const = 0;
// Platform info
virtual const char* GetPlatformName() const = 0;
virtual InputDeviceType GetPrimaryInputDevice() const = 0;
// Special platform features
virtual bool HasFeature(const char* featureName) const = 0;
// Optional: Some platforms may need special per-frame updates
virtual void Update(float deltaTime) {}
};
/**
* Platform Factory
*
* Creates the appropriate platform implementation for the current platform.
*/
class PlatformFactory
{
public:
static IPlatform* CreatePlatform();
};
/**
* Platform Entry Point Helpers
*
* Different platforms have different entry points and main loop requirements.
* These macros help define the correct entry point for each platform.
*/
// Forward declare App
class App;
App* CreateApp();
// Desktop platforms (Windows, Linux, macOS)
#if defined(PLATFORM_DESKTOP)
#define WV_PLATFORM_MAIN() \
int main(int argc, char** argv)
// Android
#elif defined(PLATFORM_ANDROID)
#define WV_PLATFORM_MAIN() \
void android_main(struct android_app* state)
// iOS
#elif defined(PLATFORM_IOS)
#define WV_PLATFORM_MAIN() \
int main(int argc, char** argv)
// PS3
#elif defined(PLATFORM_PS3)
#define WV_PLATFORM_MAIN() \
int main(int argc, char** argv)
// PS4
#elif defined(PLATFORM_PS4)
#define WV_PLATFORM_MAIN() \
int main(int argc, char** argv)
// Wii / GameCube
#elif defined(PLATFORM_WII) || defined(PLATFORM_GAMECUBE)
#define WV_PLATFORM_MAIN() \
int main(int argc, char** argv)
// Wii U
#elif defined(PLATFORM_WIIU)
#define WV_PLATFORM_MAIN() \
int main(int argc, char** argv)
// Nintendo Switch
#elif defined(PLATFORM_SWITCH)
#define WV_PLATFORM_MAIN() \
int main(int argc, char** argv)
// Xbox Series X/S Dev Mode
#elif defined(PLATFORM_XBOX_SERIES_DEV)
#define WV_PLATFORM_MAIN() \
int __cdecl main(Platform::Array<Platform::String^>^ args)
#else
#error "Unsupported platform for entry point!"
#endif
}
+178
View File
@@ -0,0 +1,178 @@
#pragma once
#include <wv/wvpch.h>
namespace WillowVox
{
/**
* Platform-Agnostic Input State
*
* This structure represents the abstract input state that the game layer sees.
* All platform-specific inputs (keyboard, mouse, touch, gamepads) are mapped
* to these abstract actions by the platform layer.
*/
// Abstract input actions that game understands
enum class InputAction
{
// Movement
MoveForward,
MoveBackward,
MoveLeft,
MoveRight,
MoveUp,
MoveDown,
// Camera/Look
LookUp,
LookDown,
LookLeft,
LookRight,
// Primary actions
Action1, // Primary action (e.g., attack, break block, confirm)
Action2, // Secondary action (e.g., use item, place block, cancel)
Action3, // Tertiary action (e.g., pick block, special)
Action4, // Quaternary action
// Menu/UI
MenuOpen, // Open main menu / pause
MenuBack, // Back / escape
MenuConfirm, // Confirm selection
MenuCancel, // Cancel selection
// Other
Jump,
Crouch,
Sprint,
Interact,
// Special
CycleLeft, // Cycle items/options left
CycleRight, // Cycle items/options right
Count
};
// Input device types
enum class InputDeviceType
{
Unknown,
KeyboardMouse,
Touchscreen,
Gamepad,
WiiRemote,
PSController,
XboxController,
SwitchController
};
// Abstract input state
struct InputState
{
// Digital buttons (pressed this frame)
bool actions[static_cast<int>(InputAction::Count)] = {};
// Digital buttons (held down)
bool actionsHeld[static_cast<int>(InputAction::Count)] = {};
// Digital buttons (released this frame)
bool actionsReleased[static_cast<int>(InputAction::Count)] = {};
// Analog inputs (-1.0 to 1.0)
float moveAxisX = 0.0f; // Left/Right movement
float moveAxisY = 0.0f; // Forward/Backward movement
float lookAxisX = 0.0f; // Horizontal look
float lookAxisY = 0.0f; // Vertical look
// Mouse/Touch position (normalized 0-1 or pixels depending on platform)
float pointerX = 0.0f;
float pointerY = 0.0f;
float pointerDeltaX = 0.0f;
float pointerDeltaY = 0.0f;
bool pointerDown = false;
// Scroll/Zoom
float scrollDelta = 0.0f;
// Device info
InputDeviceType deviceType = InputDeviceType::Unknown;
// Helper to check if action is pressed this frame
bool IsActionPressed(InputAction action) const
{
return actions[static_cast<int>(action)];
}
// Helper to check if action is held
bool IsActionHeld(InputAction action) const
{
return actionsHeld[static_cast<int>(action)];
}
// Helper to check if action was released this frame
bool IsActionReleased(InputAction action) const
{
return actionsReleased[static_cast<int>(action)];
}
// Reset per-frame input states
void ResetFrameStates()
{
for (int i = 0; i < static_cast<int>(InputAction::Count); ++i)
{
actions[i] = false;
actionsReleased[i] = false;
}
pointerDeltaX = 0.0f;
pointerDeltaY = 0.0f;
scrollDelta = 0.0f;
}
};
/**
* Touch Control Layout (for mobile platforms)
*
* Defines virtual buttons and joystick regions for touchscreen controls.
*/
struct TouchControlRegion
{
float x, y, width, height; // Normalized screen coordinates (0-1)
InputAction mappedAction;
bool isJoystick; // If true, this is a virtual joystick
};
/**
* Gamepad Button Mapping
*
* Maps physical gamepad buttons to abstract input actions.
* Each platform implements this mapping for their specific controller.
*/
struct GamepadMapping
{
// Button indices vary by platform, but we define standard mappings
// Platform layer translates native button IDs to InputActions
// Example for standard gamepad:
// Button 0 (A/Cross) -> Action1
// Button 1 (B/Circle) -> Action2
// Button 2 (X/Square) -> Action3
// Button 3 (Y/Triangle) -> Action4
// Button 4 (LB/L1) -> CycleLeft
// Button 5 (RB/R1) -> CycleRight
// Button 6 (Back/Select) -> MenuBack
// Button 7 (Start) -> MenuOpen
// Button 8 (L3) -> Crouch
// Button 9 (R3) -> Sprint
static constexpr int MAX_BUTTONS = 32;
InputAction buttonMapping[MAX_BUTTONS];
GamepadMapping()
{
// Initialize all to Count (invalid)
for (int i = 0; i < MAX_BUTTONS; ++i)
buttonMapping[i] = InputAction::Count;
}
};
}
+241
View File
@@ -0,0 +1,241 @@
#pragma once
/**
* Platform Detection and Configuration
*
* This header centralizes all platform detection logic and defines
* platform-specific macros used throughout the engine.
*
* Platform Macros Defined:
* - Desktop: PLATFORM_WINDOWS, PLATFORM_LINUX, PLATFORM_MACOS
* - Mobile: PLATFORM_ANDROID, PLATFORM_IOS
* - Consoles: PLATFORM_PS3, PLATFORM_PS4, PLATFORM_WII, PLATFORM_GAMECUBE,
* PLATFORM_WIIU, PLATFORM_SWITCH, PLATFORM_XBOX_SERIES_DEV
*
* Platform Categories:
* - PLATFORM_DESKTOP: Windows, Linux, macOS
* - PLATFORM_MOBILE: Android, iOS
* - PLATFORM_CONSOLE: All console platforms
* - PLATFORM_NINTENDO: Wii, GameCube, Wii U, Switch
* - PLATFORM_PLAYSTATION: PS3, PS4
*/
// ============================================================================
// Desktop Platforms
// ============================================================================
#if defined(_WIN32) || defined(_WIN64) || defined(__WIN32__) || defined(__WINDOWS__)
#ifndef PLATFORM_WINDOWS
#define PLATFORM_WINDOWS 1
#endif
#define PLATFORM_DESKTOP 1
#define PLATFORM_NAME "Windows"
#elif defined(__linux__) && !defined(__ANDROID__)
#ifndef PLATFORM_LINUX
#define PLATFORM_LINUX 1
#endif
#define PLATFORM_DESKTOP 1
#define PLATFORM_NAME "Linux"
#elif defined(__APPLE__) && defined(__MACH__)
#include <TargetConditionals.h>
#if TARGET_OS_IPHONE || TARGET_IPHONE_SIMULATOR
#ifndef PLATFORM_IOS
#define PLATFORM_IOS 1
#endif
#define PLATFORM_MOBILE 1
#define PLATFORM_NAME "iOS"
#else
#ifndef PLATFORM_MACOS
#define PLATFORM_MACOS 1
#endif
#define PLATFORM_DESKTOP 1
#define PLATFORM_NAME "macOS"
#endif
// ============================================================================
// Mobile Platforms
// ============================================================================
#elif defined(__ANDROID__)
#ifndef PLATFORM_ANDROID
#define PLATFORM_ANDROID 1
#endif
#define PLATFORM_MOBILE 1
#define PLATFORM_NAME "Android"
// ============================================================================
// PlayStation Consoles (Homebrew)
// ============================================================================
#elif defined(__PPU__) || defined(__CELLOS_LV2__)
// PS3 (PSL1GHT toolchain)
#ifndef PLATFORM_PS3
#define PLATFORM_PS3 1
#endif
#define PLATFORM_CONSOLE 1
#define PLATFORM_PLAYSTATION 1
#define PLATFORM_NAME "PlayStation 3"
#elif defined(__ORBIS__) || defined(__PS4__)
// PS4 (OpenOrbis toolchain)
#ifndef PLATFORM_PS4
#define PLATFORM_PS4 1
#endif
#define PLATFORM_CONSOLE 1
#define PLATFORM_PLAYSTATION 1
#define PLATFORM_NAME "PlayStation 4"
// ============================================================================
// Nintendo Consoles (devkitPro Homebrew)
// ============================================================================
#elif defined(__wii__) || defined(HW_RVL)
// Wii (devkitPPC)
#ifndef PLATFORM_WII
#define PLATFORM_WII 1
#endif
#define PLATFORM_CONSOLE 1
#define PLATFORM_NINTENDO 1
#define PLATFORM_NAME "Nintendo Wii"
#elif defined(__gamecube__) || defined(HW_DOL)
// GameCube (devkitPPC)
#ifndef PLATFORM_GAMECUBE
#define PLATFORM_GAMECUBE 1
#endif
#define PLATFORM_CONSOLE 1
#define PLATFORM_NINTENDO 1
#define PLATFORM_NAME "Nintendo GameCube"
#elif defined(__WIIU__) || defined(__wiiu__)
// Wii U (devkitPPC + wut)
#ifndef PLATFORM_WIIU
#define PLATFORM_WIIU 1
#endif
#define PLATFORM_CONSOLE 1
#define PLATFORM_NINTENDO 1
#define PLATFORM_NAME "Nintendo Wii U"
#elif defined(__SWITCH__) || defined(__switch__)
// Nintendo Switch (devkitA64 + libnx)
#ifndef PLATFORM_SWITCH
#define PLATFORM_SWITCH 1
#endif
#define PLATFORM_CONSOLE 1
#define PLATFORM_NINTENDO 1
#define PLATFORM_NAME "Nintendo Switch"
// ============================================================================
// Xbox (Dev Mode / GDK)
// ============================================================================
#elif defined(_GAMING_XBOX) || defined(_GAMING_XBOX_SCARLETT)
// Xbox Series X/S in Dev Mode or GDK
#ifndef PLATFORM_XBOX_SERIES_DEV
#define PLATFORM_XBOX_SERIES_DEV 1
#endif
#define PLATFORM_CONSOLE 1
#define PLATFORM_XBOX 1
#define PLATFORM_NAME "Xbox Series X/S (Dev Mode)"
#else
#error "Unknown or unsupported platform!"
#endif
// ============================================================================
// Platform Feature Detection
// ============================================================================
// Has mouse & keyboard
#if defined(PLATFORM_DESKTOP)
#define PLATFORM_HAS_MOUSE 1
#define PLATFORM_HAS_KEYBOARD 1
#endif
// Has touchscreen
#if defined(PLATFORM_MOBILE)
#define PLATFORM_HAS_TOUCHSCREEN 1
#endif
// Has gamepad/controller
#if defined(PLATFORM_CONSOLE) || defined(PLATFORM_DESKTOP)
#define PLATFORM_HAS_GAMEPAD 1
#endif
// Has filesystem access
#if !defined(PLATFORM_WEB)
#define PLATFORM_HAS_FILESYSTEM 1
#endif
// ============================================================================
// Graphics API Hints
// ============================================================================
#if defined(PLATFORM_DESKTOP)
// Desktop typically uses OpenGL or Vulkan
#define PLATFORM_GRAPHICS_OPENGL 1
#elif defined(PLATFORM_ANDROID)
// Android uses OpenGL ES or Vulkan
#define PLATFORM_GRAPHICS_OPENGL_ES 1
#elif defined(PLATFORM_IOS)
// iOS uses Metal (but we can also use OpenGL ES compatibility)
#define PLATFORM_GRAPHICS_METAL 1
#define PLATFORM_GRAPHICS_OPENGL_ES 1
#elif defined(PLATFORM_SWITCH)
// Switch uses OpenGL or native NVN
#define PLATFORM_GRAPHICS_OPENGL 1
#elif defined(PLATFORM_PS3)
// PS3 uses GCM or PSGL (OpenGL subset)
#define PLATFORM_GRAPHICS_GCM 1
#elif defined(PLATFORM_PS4)
// PS4 uses GNM
#define PLATFORM_GRAPHICS_GNM 1
#elif defined(PLATFORM_WII) || defined(PLATFORM_GAMECUBE)
// Wii/GC use GX
#define PLATFORM_GRAPHICS_GX 1
#elif defined(PLATFORM_WIIU)
// Wii U uses GX2
#define PLATFORM_GRAPHICS_GX2 1
#elif defined(PLATFORM_XBOX_SERIES_DEV)
// Xbox uses DirectX 12
#define PLATFORM_GRAPHICS_DX12 1
#endif
// ============================================================================
// Compiler Attributes
// ============================================================================
#if defined(__GNUC__) || defined(__clang__)
#define WV_FORCE_INLINE __attribute__((always_inline)) inline
#define WV_NO_INLINE __attribute__((noinline))
#elif defined(_MSC_VER)
#define WV_FORCE_INLINE __forceinline
#define WV_NO_INLINE __declspec(noinline)
#else
#define WV_FORCE_INLINE inline
#define WV_NO_INLINE
#endif
// ============================================================================
// Debug/Release Configuration
// ============================================================================
#if defined(DEBUG) || defined(_DEBUG) || !defined(NDEBUG)
#define WV_DEBUG 1
#define WV_RELEASE 0
#else
#define WV_DEBUG 0
#define WV_RELEASE 1
#endif
// ============================================================================
// Platform-Specific Includes
// ============================================================================
// Standard library availability varies by platform
#if defined(PLATFORM_PS3) || defined(PLATFORM_WII) || defined(PLATFORM_GAMECUBE)
// Some older consoles have limited standard library support
#define WV_LIMITED_STL 1
#endif
+76 -22
View File
@@ -1,9 +1,8 @@
#include <wv/app/App.h>
#include <wv/Logger.h>
#include <wv/rendering/Renderer.h>
#include <wv/rendering/Window.h>
#include <wv/input/Input.h>
#include <wv/platform/IPlatform.h>
#include <wv/platform/IGraphicsContext.h>
#include <iostream>
namespace WillowVox
@@ -13,39 +12,94 @@ namespace WillowVox
void App::Run()
{
Logger::EngineLog("Using WillowVox Engine");
Renderer::Init();
Window::InitWindow(appDefaultWindowX, appDefaultWindowY, appWindowName);
auto& window = Window::GetInstance();
window.SetBackgroundColor(0.1f, 0.1f, 0.1f, 1.0f);
Logger::EngineLog("Using WillowVox Engine");
Input::Init();
// Create platform abstraction
m_platform = PlatformFactory::CreatePlatform();
if (!m_platform)
{
Logger::EngineError("Failed to create platform abstraction!");
return;
}
// Initialize platform
if (!m_platform->Initialize())
{
Logger::EngineError("Failed to initialize platform!");
delete m_platform;
return;
}
Logger::EngineLog("Running on platform: %s", m_platform->GetPlatformName());
// Get graphics context
m_graphicsContext = m_platform->GetGraphicsContext();
if (!m_graphicsContext)
{
Logger::EngineError("Failed to get graphics context!");
m_platform->Shutdown();
delete m_platform;
return;
}
// Initialize graphics
if (!m_graphicsContext->Initialize(appDefaultWindowX, appDefaultWindowY, appWindowName))
{
Logger::EngineError("Failed to initialize graphics context!");
m_platform->Shutdown();
delete m_platform;
return;
}
m_graphicsContext->SetBackgroundColor(0.1f, 0.1f, 0.1f, 1.0f);
// Call user's Start function
Start();
while(!window.ShouldClose())
// Main loop
while(!m_graphicsContext->ShouldClose())
{
// Calculate deltaTime
float frameStartTime = Renderer::GetTime();
float currentFrame = frameStartTime;
float currentFrame = m_graphicsContext->GetTime();
m_deltaTime = currentFrame - m_lastFrame;
m_lastFrame = currentFrame;
// Clear window
window.Clear();
// Process platform events
m_platform->ProcessEvents();
// Poll input
m_platform->PollInput(m_inputState);
// Begin frame
m_graphicsContext->BeginFrame();
// Clear
m_graphicsContext->Clear(0.1f, 0.1f, 0.1f, 1.0f);
// Client app logic
Update();
Update(m_inputState);
// Client rendering
Render();
// End-of-frame steps
Input::ResetStates();
window.SwapBuffers();
window.PollEvents();
// End frame
m_graphicsContext->EndFrame();
// Swap buffers
m_graphicsContext->SwapBuffers();
// Reset per-frame input states
m_platform->ResetInputFrameState(m_inputState);
// Platform-specific per-frame update
m_platform->Update(m_deltaTime);
}
Renderer::Shutdown();
// Shutdown
m_graphicsContext->Shutdown();
m_platform->Shutdown();
delete m_platform;
Logger::EngineLog("WillowVox Engine shutdown");
}
}
@@ -0,0 +1,278 @@
#include "AndroidGraphicsContext.h"
#include <wv/Logger.h>
#ifdef PLATFORM_ANDROID
#include <EGL/egl.h>
#include <GLES3/gl3.h>
#include <android/native_window.h>
#include <android/asset_manager.h>
#include <time.h>
namespace WillowVox
{
AndroidGraphicsContext::AndroidGraphicsContext()
{
// Get start time
struct timespec ts;
clock_gettime(CLOCK_MONOTONIC, &ts);
m_startTime = ts.tv_sec + ts.tv_nsec / 1000000000.0f;
}
AndroidGraphicsContext::~AndroidGraphicsContext()
{
Shutdown();
}
void AndroidGraphicsContext::SetNativeWindow(ANativeWindow* window)
{
m_nativeWindow = window;
}
void AndroidGraphicsContext::SetAssetManager(AAssetManager* assetManager)
{
m_assetManager = assetManager;
}
bool AndroidGraphicsContext::Initialize(int width, int height, const char* title)
{
m_width = width;
m_height = height;
if (!m_nativeWindow)
{
Logger::EngineError("Android: No native window set!");
return false;
}
if (!InitializeEGL())
{
Logger::EngineError("Failed to initialize EGL");
return false;
}
// Enable OpenGL ES features
glEnable(GL_DEPTH_TEST);
glEnable(GL_CULL_FACE);
glCullFace(GL_BACK);
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
Logger::EngineLog("Android Graphics Context initialized (OpenGL ES %s)",
glGetString(GL_VERSION));
m_initialized = true;
return true;
}
bool AndroidGraphicsContext::InitializeEGL()
{
// Get default display
EGLDisplay display = eglGetDisplay(EGL_DEFAULT_DISPLAY);
if (display == EGL_NO_DISPLAY)
{
Logger::EngineError("eglGetDisplay failed");
return false;
}
// Initialize EGL
if (!eglInitialize(display, nullptr, nullptr))
{
Logger::EngineError("eglInitialize failed");
return false;
}
// Choose config
const EGLint attribs[] = {
EGL_RENDERABLE_TYPE, EGL_OPENGL_ES3_BIT,
EGL_SURFACE_TYPE, EGL_WINDOW_BIT,
EGL_BLUE_SIZE, 8,
EGL_GREEN_SIZE, 8,
EGL_RED_SIZE, 8,
EGL_ALPHA_SIZE, 8,
EGL_DEPTH_SIZE, 24,
EGL_STENCIL_SIZE, 8,
EGL_NONE
};
EGLConfig config;
EGLint numConfigs;
if (!eglChooseConfig(display, attribs, &config, 1, &numConfigs))
{
Logger::EngineError("eglChooseConfig failed");
return false;
}
// Create surface
EGLSurface surface = eglCreateWindowSurface(display, config, m_nativeWindow, nullptr);
if (surface == EGL_NO_SURFACE)
{
Logger::EngineError("eglCreateWindowSurface failed");
return false;
}
// Create context
const EGLint contextAttribs[] = {
EGL_CONTEXT_CLIENT_VERSION, 3,
EGL_NONE
};
EGLContext context = eglCreateContext(display, config, EGL_NO_CONTEXT, contextAttribs);
if (context == EGL_NO_CONTEXT)
{
Logger::EngineError("eglCreateContext failed");
return false;
}
// Make current
if (!eglMakeCurrent(display, surface, surface, context))
{
Logger::EngineError("eglMakeCurrent failed");
return false;
}
// Store handles
m_eglDisplay = display;
m_eglSurface = surface;
m_eglContext = context;
// Get actual surface size
eglQuerySurface(display, surface, EGL_WIDTH, &m_width);
eglQuerySurface(display, surface, EGL_HEIGHT, &m_height);
// Set VSync
eglSwapInterval(display, m_vsyncEnabled ? 1 : 0);
return true;
}
void AndroidGraphicsContext::Shutdown()
{
if (m_initialized)
{
ShutdownEGL();
m_initialized = false;
}
}
void AndroidGraphicsContext::ShutdownEGL()
{
if (m_eglDisplay != EGL_NO_DISPLAY)
{
eglMakeCurrent((EGLDisplay)m_eglDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
if (m_eglContext != EGL_NO_CONTEXT)
{
eglDestroyContext((EGLDisplay)m_eglDisplay, (EGLContext)m_eglContext);
m_eglContext = EGL_NO_CONTEXT;
}
if (m_eglSurface != EGL_NO_SURFACE)
{
eglDestroySurface((EGLDisplay)m_eglDisplay, (EGLSurface)m_eglSurface);
m_eglSurface = EGL_NO_SURFACE;
}
eglTerminate((EGLDisplay)m_eglDisplay);
m_eglDisplay = EGL_NO_DISPLAY;
}
}
void AndroidGraphicsContext::BeginFrame()
{
// Nothing specific needed
}
void AndroidGraphicsContext::EndFrame()
{
// Nothing specific needed
}
void AndroidGraphicsContext::SwapBuffers()
{
if (m_eglDisplay && m_eglSurface)
{
eglSwapBuffers((EGLDisplay)m_eglDisplay, (EGLSurface)m_eglSurface);
}
}
void AndroidGraphicsContext::Clear(float r, float g, float b, float a)
{
glClearColor(r, g, b, a);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
}
void AndroidGraphicsContext::ClearDepth()
{
glClear(GL_DEPTH_BUFFER_BIT);
}
bool AndroidGraphicsContext::ShouldClose() const
{
return m_shouldClose;
}
void AndroidGraphicsContext::SetShouldClose(bool shouldClose)
{
m_shouldClose = shouldClose;
}
void AndroidGraphicsContext::GetFramebufferSize(int& width, int& height) const
{
width = m_width;
height = m_height;
}
void AndroidGraphicsContext::GetWindowSize(int& width, int& height) const
{
width = m_width;
height = m_height;
}
void AndroidGraphicsContext::SetVSync(bool enabled)
{
m_vsyncEnabled = enabled;
if (m_eglDisplay)
{
eglSwapInterval((EGLDisplay)m_eglDisplay, enabled ? 1 : 0);
}
}
bool AndroidGraphicsContext::IsVSyncEnabled() const
{
return m_vsyncEnabled;
}
float AndroidGraphicsContext::GetTime() const
{
struct timespec ts;
clock_gettime(CLOCK_MONOTONIC, &ts);
float currentTime = ts.tv_sec + ts.tv_nsec / 1000000000.0f;
return currentTime - m_startTime;
}
void AndroidGraphicsContext::SetBackgroundColor(float r, float g, float b, float a)
{
m_clearColor[0] = r;
m_clearColor[1] = g;
m_clearColor[2] = b;
m_clearColor[3] = a;
}
void* AndroidGraphicsContext::GetNativeWindowHandle() const
{
return m_nativeWindow;
}
void* AndroidGraphicsContext::GetNativeGraphicsHandle() const
{
return m_eglContext;
}
void AndroidGraphicsContext::SetViewport(int x, int y, int width, int height)
{
glViewport(x, y, width, height);
}
}
#endif // PLATFORM_ANDROID
@@ -0,0 +1,78 @@
#pragma once
#include <wv/platform/IGraphicsContext.h>
// Forward declarations (avoid including Android headers in header file)
struct ANativeWindow;
struct AAssetManager;
namespace WillowVox
{
/**
* Android Graphics Context (OpenGL ES + EGL)
*
* Implements IGraphicsContext for Android using OpenGL ES 3.0+ and EGL
* for context creation. Uses ANativeWindow for the rendering surface.
*/
class AndroidGraphicsContext : public IGraphicsContext
{
public:
AndroidGraphicsContext();
~AndroidGraphicsContext() override;
// IGraphicsContext implementation
bool Initialize(int width, int height, const char* title) override;
void Shutdown() override;
void BeginFrame() override;
void EndFrame() override;
void SwapBuffers() override;
void Clear(float r, float g, float b, float a) override;
void ClearDepth() override;
bool ShouldClose() const override;
void SetShouldClose(bool shouldClose) override;
void GetFramebufferSize(int& width, int& height) const override;
void GetWindowSize(int& width, int& height) const override;
void SetVSync(bool enabled) override;
bool IsVSyncEnabled() const override;
float GetTime() const override;
void SetBackgroundColor(float r, float g, float b, float a) override;
void* GetNativeWindowHandle() const override;
void* GetNativeGraphicsHandle() const override;
void SetViewport(int x, int y, int width, int height) override;
// Android-specific
void SetNativeWindow(ANativeWindow* window);
void SetAssetManager(AAssetManager* assetManager);
private:
bool InitializeEGL();
void ShutdownEGL();
ANativeWindow* m_nativeWindow = nullptr;
AAssetManager* m_assetManager = nullptr;
// EGL handles (using void* to avoid including EGL headers)
void* m_eglDisplay = nullptr;
void* m_eglSurface = nullptr;
void* m_eglContext = nullptr;
bool m_initialized = false;
bool m_shouldClose = false;
bool m_vsyncEnabled = true;
float m_clearColor[4] = {0.1f, 0.1f, 0.1f, 1.0f};
int m_width = 0;
int m_height = 0;
float m_startTime = 0.0f;
};
}
+432
View File
@@ -0,0 +1,432 @@
#include "AndroidPlatform.h"
#include <wv/Logger.h>
#ifdef PLATFORM_ANDROID
#include <android/input.h>
#include <android_native_app_glue.h>
#include <android/native_activity.h>
#include <cmath>
#include <cstring>
namespace WillowVox
{
AndroidPlatform::AndroidPlatform()
{
}
AndroidPlatform::~AndroidPlatform()
{
Shutdown();
}
void AndroidPlatform::SetAndroidApp(android_app* app)
{
m_androidApp = app;
}
bool AndroidPlatform::Initialize()
{
Logger::EngineLog("Initializing Android Platform");
if (!m_androidApp)
{
Logger::EngineError("Android app not set!");
return false;
}
m_graphicsContext = std::make_unique<AndroidGraphicsContext>();
m_graphicsContext->SetNativeWindow(m_androidApp->window);
m_graphicsContext->SetAssetManager(m_androidApp->activity->assetManager);
// Get screen dimensions
if (m_androidApp->window)
{
m_screenWidth = ANativeWindow_getWidth(m_androidApp->window);
m_screenHeight = ANativeWindow_getHeight(m_androidApp->window);
Logger::EngineLog("Screen size: %dx%d", m_screenWidth, m_screenHeight);
}
// Setup virtual controls
SetupVirtualControls();
return true;
}
void AndroidPlatform::SetupVirtualControls()
{
m_virtualControls.clear();
// Left virtual joystick (bottom left corner)
// Position at (0.15, 0.85) with radius 0.12
m_virtualControls.push_back({0.15f, 0.85f, 0.12f, InputAction::Count, true});
// Action buttons (bottom right)
// A button (primary action)
m_virtualControls.push_back({0.85f, 0.85f, 0.06f, InputAction::Action1, false});
// B button (secondary action)
m_virtualControls.push_back({0.75f, 0.80f, 0.06f, InputAction::Action2, false});
// Jump button (X)
m_virtualControls.push_back({0.85f, 0.75f, 0.06f, InputAction::Jump, false});
// Menu button (top left)
m_virtualControls.push_back({0.10f, 0.10f, 0.05f, InputAction::MenuOpen, false});
Logger::EngineLog("Virtual controls setup complete");
}
void AndroidPlatform::Shutdown()
{
m_graphicsContext.reset();
Logger::EngineLog("Android Platform shutdown");
}
void AndroidPlatform::ProcessEvents()
{
if (!m_androidApp)
return;
// Process pending events
int events;
struct android_poll_source* source;
// Non-blocking poll
while (ALooper_pollAll(0, nullptr, &events, (void**)&source) >= 0)
{
if (source != nullptr)
{
source->process(m_androidApp, source);
}
// Check if we're exiting
if (m_androidApp->destroyRequested != 0)
{
m_graphicsContext->SetShouldClose(true);
}
}
}
int32_t AndroidPlatform::HandleInputEvent(AInputEvent* event)
{
int32_t eventType = AInputEvent_getType(event);
if (eventType == AINPUT_EVENT_TYPE_MOTION)
{
int32_t action = AMotionEvent_getAction(event);
int32_t actionMasked = action & AMOTION_EVENT_ACTION_MASK;
size_t pointerIndex = (action & AMOTION_EVENT_ACTION_POINTER_INDEX_MASK)
>> AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT;
int32_t pointerId = AMotionEvent_getPointerId(event, pointerIndex);
float x = AMotionEvent_getX(event, pointerIndex);
float y = AMotionEvent_getY(event, pointerIndex);
// Normalize to 0-1
float normX = x / m_screenWidth;
float normY = y / m_screenHeight;
switch (actionMasked)
{
case AMOTION_EVENT_ACTION_DOWN:
case AMOTION_EVENT_ACTION_POINTER_DOWN:
{
TouchPoint* touch = AllocateTouchPoint(pointerId);
if (touch)
{
touch->x = normX;
touch->y = normY;
touch->startX = normX;
touch->startY = normY;
}
break;
}
case AMOTION_EVENT_ACTION_MOVE:
{
// Update all active touch points
size_t pointerCount = AMotionEvent_getPointerCount(event);
for (size_t i = 0; i < pointerCount; ++i)
{
int32_t id = AMotionEvent_getPointerId(event, i);
TouchPoint* touch = GetTouchPoint(id);
if (touch)
{
touch->x = AMotionEvent_getX(event, i) / m_screenWidth;
touch->y = AMotionEvent_getY(event, i) / m_screenHeight;
}
}
break;
}
case AMOTION_EVENT_ACTION_UP:
case AMOTION_EVENT_ACTION_POINTER_UP:
case AMOTION_EVENT_ACTION_CANCEL:
{
ReleaseTouchPoint(pointerId);
break;
}
}
return 1; // Event handled
}
return 0; // Event not handled
}
void AndroidPlatform::PollInput(InputState& outInputState)
{
ProcessTouchInput(outInputState);
// Copy button states
std::memcpy(m_buttonPrevStates, m_buttonStates, sizeof(m_buttonStates));
outInputState.deviceType = InputDeviceType::Touchscreen;
}
void AndroidPlatform::ProcessTouchInput(InputState& outInputState)
{
// Reset per-frame state
m_lookDeltaX = 0.0f;
m_lookDeltaY = 0.0f;
m_joystickX = 0.0f;
m_joystickY = 0.0f;
// Reset button states
for (int i = 0; i < static_cast<int>(InputAction::Count); ++i)
{
m_buttonStates[i] = false;
}
// Process each active touch point
for (int i = 0; i < MAX_TOUCH_POINTS; ++i)
{
TouchPoint& touch = m_touchPoints[i];
if (!touch.active)
continue;
// Check virtual controls
bool handled = false;
// Virtual joystick (left side)
if (touch.startX < 0.5f && touch.id == m_joystickTouchId)
{
// Calculate joystick displacement from start position
float dx = touch.x - touch.startX;
float dy = touch.y - touch.startY;
// Normalize joystick input (clamp to radius)
float dist = std::sqrt(dx * dx + dy * dy);
float maxDist = 0.12f; // Same as virtual control radius
if (dist > maxDist)
{
dx = (dx / dist) * maxDist;
dy = (dy / dist) * maxDist;
}
m_joystickX = dx / maxDist;
m_joystickY = dy / maxDist;
handled = true;
}
else if (touch.startX < 0.5f && m_joystickTouchId == -1)
{
// New touch on left side - assign to joystick
m_joystickTouchId = touch.id;
handled = true;
}
// Look control (right side)
if (touch.startX >= 0.5f && touch.id == m_lookTouchId)
{
// Calculate look delta
static float prevLookX = touch.startX;
static float prevLookY = touch.startY;
m_lookDeltaX = (touch.x - prevLookX) * m_screenWidth;
m_lookDeltaY = (touch.y - prevLookY) * m_screenHeight;
prevLookX = touch.x;
prevLookY = touch.y;
handled = true;
}
else if (touch.startX >= 0.5f && m_lookTouchId == -1)
{
// Check if touching a button first
bool isTouchingButton = false;
for (const auto& control : m_virtualControls)
{
if (!control.isJoystick &&
IsInsideCircle(touch.x, touch.y, control.x, control.y, control.radius))
{
m_buttonStates[static_cast<int>(control.action)] = true;
isTouchingButton = true;
handled = true;
break;
}
}
// If not touching a button, use for look control
if (!isTouchingButton)
{
m_lookTouchId = touch.id;
handled = true;
}
}
// Check button touches
if (!handled)
{
for (const auto& control : m_virtualControls)
{
if (!control.isJoystick &&
IsInsideCircle(touch.x, touch.y, control.x, control.y, control.radius))
{
m_buttonStates[static_cast<int>(control.action)] = true;
break;
}
}
}
}
// Check for released touch IDs
bool joystickActive = false;
bool lookActive = false;
for (int i = 0; i < MAX_TOUCH_POINTS; ++i)
{
if (m_touchPoints[i].active)
{
if (m_touchPoints[i].id == m_joystickTouchId)
joystickActive = true;
if (m_touchPoints[i].id == m_lookTouchId)
lookActive = true;
}
}
if (!joystickActive)
m_joystickTouchId = -1;
if (!lookActive)
m_lookTouchId = -1;
// Map to InputState
outInputState.moveAxisX = m_joystickX;
outInputState.moveAxisY = -m_joystickY; // Invert Y
outInputState.lookAxisX = m_lookDeltaX;
outInputState.lookAxisY = m_lookDeltaY;
outInputState.pointerDeltaX = m_lookDeltaX;
outInputState.pointerDeltaY = m_lookDeltaY;
// Map analog to digital actions
if (std::abs(m_joystickX) > 0.5f || std::abs(m_joystickY) > 0.5f)
{
if (m_joystickY < -0.5f)
outInputState.actionsHeld[static_cast<int>(InputAction::MoveForward)] = true;
if (m_joystickY > 0.5f)
outInputState.actionsHeld[static_cast<int>(InputAction::MoveBackward)] = true;
if (m_joystickX < -0.5f)
outInputState.actionsHeld[static_cast<int>(InputAction::MoveLeft)] = true;
if (m_joystickX > 0.5f)
outInputState.actionsHeld[static_cast<int>(InputAction::MoveRight)] = true;
}
// Copy button states
for (int i = 0; i < static_cast<int>(InputAction::Count); ++i)
{
outInputState.actionsHeld[i] = m_buttonStates[i];
outInputState.actions[i] = m_buttonStates[i] && !m_buttonPrevStates[i];
outInputState.actionsReleased[i] = !m_buttonStates[i] && m_buttonPrevStates[i];
}
}
void AndroidPlatform::ResetInputFrameState(InputState& inputState)
{
inputState.ResetFrameStates();
}
TouchPoint* AndroidPlatform::GetTouchPoint(int32_t id)
{
for (int i = 0; i < MAX_TOUCH_POINTS; ++i)
{
if (m_touchPoints[i].active && m_touchPoints[i].id == id)
return &m_touchPoints[i];
}
return nullptr;
}
TouchPoint* AndroidPlatform::AllocateTouchPoint(int32_t id)
{
for (int i = 0; i < MAX_TOUCH_POINTS; ++i)
{
if (!m_touchPoints[i].active)
{
m_touchPoints[i].active = true;
m_touchPoints[i].id = id;
return &m_touchPoints[i];
}
}
return nullptr;
}
void AndroidPlatform::ReleaseTouchPoint(int32_t id)
{
for (int i = 0; i < MAX_TOUCH_POINTS; ++i)
{
if (m_touchPoints[i].active && m_touchPoints[i].id == id)
{
m_touchPoints[i].active = false;
m_touchPoints[i].id = -1;
return;
}
}
}
bool AndroidPlatform::IsInsideCircle(float x, float y, float cx, float cy, float radius) const
{
float dx = x - cx;
float dy = y - cy;
return (dx * dx + dy * dy) <= (radius * radius);
}
IGraphicsContext* AndroidPlatform::GetGraphicsContext()
{
return m_graphicsContext.get();
}
const char* AndroidPlatform::GetUserDataPath() const
{
if (m_androidApp && m_androidApp->activity)
{
return m_androidApp->activity->internalDataPath;
}
return "/sdcard/WillowVox";
}
const char* AndroidPlatform::GetAssetsPath() const
{
return ""; // Android uses AssetManager, not file paths
}
const char* AndroidPlatform::GetPlatformName() const
{
return "Android";
}
InputDeviceType AndroidPlatform::GetPrimaryInputDevice() const
{
return InputDeviceType::Touchscreen;
}
bool AndroidPlatform::HasFeature(const char* featureName) const
{
if (strcmp(featureName, "touchscreen") == 0) return true;
if (strcmp(featureName, "filesystem") == 0) return true;
return false;
}
}
#endif // PLATFORM_ANDROID
+111
View File
@@ -0,0 +1,111 @@
#pragma once
#include <wv/platform/IPlatform.h>
#include "AndroidGraphicsContext.h"
#include <memory>
#include <vector>
// Forward declarations
struct android_app;
struct AInputEvent;
namespace WillowVox
{
/**
* Android Platform Implementation
*
* Implements IPlatform for Android using android_native_app_glue.
*
* Virtual Touch Controls Layout:
* - Left side: Virtual joystick (movement)
* - Right side: Look control (drag to look around)
* - Bottom right buttons:
* - A button: Action1 (break block / primary)
* - B button: Action2 (place block / secondary)
* - X button: Jump
* - Top left: Menu button (pause/settings)
*
* Touch regions are defined in normalized screen space (0-1).
*/
class AndroidPlatform : public IPlatform
{
public:
AndroidPlatform();
~AndroidPlatform() override;
// IPlatform implementation
bool Initialize() override;
void Shutdown() override;
void PollInput(InputState& outInputState) override;
void ResetInputFrameState(InputState& inputState) override;
void ProcessEvents() override;
IGraphicsContext* GetGraphicsContext() override;
const char* GetUserDataPath() const override;
const char* GetAssetsPath() const override;
const char* GetPlatformName() const override;
InputDeviceType GetPrimaryInputDevice() const override;
bool HasFeature(const char* featureName) const override;
// Android-specific
void SetAndroidApp(android_app* app);
android_app* GetAndroidApp() const { return m_androidApp; }
// Input event handling
int32_t HandleInputEvent(AInputEvent* event);
private:
struct TouchPoint
{
int32_t id;
float x, y;
float startX, startY;
bool active;
};
std::unique_ptr<AndroidGraphicsContext> m_graphicsContext;
android_app* m_androidApp = nullptr;
// Touch tracking
static constexpr int MAX_TOUCH_POINTS = 10;
TouchPoint m_touchPoints[MAX_TOUCH_POINTS] = {};
// Virtual control regions (normalized 0-1)
struct VirtualControl
{
float x, y, radius;
InputAction action;
bool isJoystick;
};
std::vector<VirtualControl> m_virtualControls;
// Input state
float m_joystickX = 0.0f;
float m_joystickY = 0.0f;
int m_joystickTouchId = -1;
float m_lookDeltaX = 0.0f;
float m_lookDeltaY = 0.0f;
int m_lookTouchId = -1;
bool m_buttonStates[static_cast<int>(InputAction::Count)] = {};
bool m_buttonPrevStates[static_cast<int>(InputAction::Count)] = {};
int m_screenWidth = 1920;
int m_screenHeight = 1080;
// Helper methods
void SetupVirtualControls();
void ProcessTouchInput(InputState& outInputState);
TouchPoint* GetTouchPoint(int32_t id);
TouchPoint* AllocateTouchPoint(int32_t id);
void ReleaseTouchPoint(int32_t id);
bool IsInsideCircle(float x, float y, float cx, float cy, float radius) const;
};
}
@@ -0,0 +1,200 @@
#include "DesktopGraphicsContext.h"
#include <wv/Logger.h>
#include <glad/glad.h>
#include <GLFW/glfw3.h>
namespace WillowVox
{
static void GLFWErrorCallback(int error, const char* description)
{
Logger::EngineError("GLFW Error (%d): %s", error, description);
}
DesktopGraphicsContext::DesktopGraphicsContext()
{
}
DesktopGraphicsContext::~DesktopGraphicsContext()
{
Shutdown();
}
bool DesktopGraphicsContext::Initialize(int width, int height, const char* title)
{
m_width = width;
m_height = height;
// Initialize GLFW
glfwSetErrorCallback(GLFWErrorCallback);
if (!glfwInit())
{
Logger::EngineError("Failed to initialize GLFW");
return false;
}
// Configure GLFW
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
#ifdef __APPLE__
glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);
#endif
// Create window
m_window = glfwCreateWindow(width, height, title, nullptr, nullptr);
if (!m_window)
{
Logger::EngineError("Failed to create GLFW window");
glfwTerminate();
return false;
}
glfwMakeContextCurrent(m_window);
// Load OpenGL functions
if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress))
{
Logger::EngineError("Failed to initialize GLAD");
glfwDestroyWindow(m_window);
glfwTerminate();
return false;
}
// Set VSync
glfwSwapInterval(m_vsyncEnabled ? 1 : 0);
// Enable depth testing
glEnable(GL_DEPTH_TEST);
glEnable(GL_CULL_FACE);
glCullFace(GL_BACK);
// Enable blending
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
Logger::EngineLog("Desktop Graphics Context initialized (OpenGL %s)", glGetString(GL_VERSION));
return true;
}
void DesktopGraphicsContext::Shutdown()
{
if (m_window)
{
glfwDestroyWindow(m_window);
m_window = nullptr;
}
glfwTerminate();
}
void DesktopGraphicsContext::BeginFrame()
{
// Nothing special needed for GLFW
}
void DesktopGraphicsContext::EndFrame()
{
// Nothing special needed for GLFW
}
void DesktopGraphicsContext::SwapBuffers()
{
if (m_window)
{
glfwSwapBuffers(m_window);
}
}
void DesktopGraphicsContext::Clear(float r, float g, float b, float a)
{
glClearColor(r, g, b, a);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
}
void DesktopGraphicsContext::ClearDepth()
{
glClear(GL_DEPTH_BUFFER_BIT);
}
bool DesktopGraphicsContext::ShouldClose() const
{
return m_window ? glfwWindowShouldClose(m_window) : true;
}
void DesktopGraphicsContext::SetShouldClose(bool shouldClose)
{
if (m_window)
{
glfwSetWindowShouldClose(m_window, shouldClose ? GLFW_TRUE : GLFW_FALSE);
}
}
void DesktopGraphicsContext::GetFramebufferSize(int& width, int& height) const
{
if (m_window)
{
glfwGetFramebufferSize(m_window, &width, &height);
}
else
{
width = m_width;
height = m_height;
}
}
void DesktopGraphicsContext::GetWindowSize(int& width, int& height) const
{
if (m_window)
{
glfwGetWindowSize(m_window, &width, &height);
}
else
{
width = m_width;
height = m_height;
}
}
void DesktopGraphicsContext::SetVSync(bool enabled)
{
m_vsyncEnabled = enabled;
glfwSwapInterval(enabled ? 1 : 0);
}
bool DesktopGraphicsContext::IsVSyncEnabled() const
{
return m_vsyncEnabled;
}
float DesktopGraphicsContext::GetTime() const
{
return static_cast<float>(glfwGetTime());
}
void DesktopGraphicsContext::SetBackgroundColor(float r, float g, float b, float a)
{
m_clearColor[0] = r;
m_clearColor[1] = g;
m_clearColor[2] = b;
m_clearColor[3] = a;
}
void* DesktopGraphicsContext::GetNativeWindowHandle() const
{
return m_window;
}
void* DesktopGraphicsContext::GetNativeGraphicsHandle() const
{
// For OpenGL, this could return the GL context
return nullptr;
}
void DesktopGraphicsContext::SetViewport(int x, int y, int width, int height)
{
glViewport(x, y, width, height);
}
}
@@ -0,0 +1,61 @@
#pragma once
#include <wv/platform/IGraphicsContext.h>
// Forward declare GLFW types to avoid including GLFW in header
struct GLFWwindow;
namespace WillowVox
{
/**
* Desktop Graphics Context (OpenGL + GLFW)
*
* Implements IGraphicsContext for desktop platforms (Windows, Linux, macOS)
* using GLFW for windowing and OpenGL for rendering.
*/
class DesktopGraphicsContext : public IGraphicsContext
{
public:
DesktopGraphicsContext();
~DesktopGraphicsContext() override;
// IGraphicsContext implementation
bool Initialize(int width, int height, const char* title) override;
void Shutdown() override;
void BeginFrame() override;
void EndFrame() override;
void SwapBuffers() override;
void Clear(float r, float g, float b, float a) override;
void ClearDepth() override;
bool ShouldClose() const override;
void SetShouldClose(bool shouldClose) override;
void GetFramebufferSize(int& width, int& height) const override;
void GetWindowSize(int& width, int& height) const override;
void SetVSync(bool enabled) override;
bool IsVSyncEnabled() const override;
float GetTime() const override;
void SetBackgroundColor(float r, float g, float b, float a) override;
void* GetNativeWindowHandle() const override;
void* GetNativeGraphicsHandle() const override;
void SetViewport(int x, int y, int width, int height) override;
// Desktop-specific
GLFWwindow* GetGLFWWindow() const { return m_window; }
private:
GLFWwindow* m_window = nullptr;
bool m_vsyncEnabled = true;
float m_clearColor[4] = {0.1f, 0.1f, 0.1f, 1.0f};
int m_width = 0;
int m_height = 0;
};
}
+296
View File
@@ -0,0 +1,296 @@
#include "DesktopPlatform.h"
#include <wv/Logger.h>
#include <GLFW/glfw3.h>
#include <cstring>
namespace WillowVox
{
// GLFW scroll callback
static double g_scrollX = 0.0;
static double g_scrollY = 0.0;
static void GLFWScrollCallback(GLFWwindow* window, double xoffset, double yoffset)
{
g_scrollX += xoffset;
g_scrollY += yoffset;
}
DesktopPlatform::DesktopPlatform()
{
}
DesktopPlatform::~DesktopPlatform()
{
Shutdown();
}
bool DesktopPlatform::Initialize()
{
Logger::EngineLog("Initializing Desktop Platform (%s)", PLATFORM_NAME);
m_graphicsContext = std::make_unique<DesktopGraphicsContext>();
// Graphics context will initialize GLFW
return true;
}
void DesktopPlatform::Shutdown()
{
m_graphicsContext.reset();
Logger::EngineLog("Desktop Platform shutdown");
}
void DesktopPlatform::ProcessEvents()
{
glfwPollEvents();
// Store current scroll values
m_scrollX = g_scrollX;
m_scrollY = g_scrollY;
// Reset scroll accumulator for next frame
g_scrollX = 0.0;
g_scrollY = 0.0;
}
void DesktopPlatform::PollInput(InputState& outInputState)
{
GLFWwindow* window = m_graphicsContext->GetGLFWWindow();
if (!window)
return;
// Setup scroll callback if not already set
static bool scrollCallbackSet = false;
if (!scrollCallbackSet)
{
glfwSetScrollCallback(window, GLFWScrollCallback);
scrollCallbackSet = true;
}
// Update keyboard
UpdateKeyboardInput(outInputState);
// Update mouse
UpdateMouseInput(outInputState);
// Update gamepad (if connected)
UpdateGamepadInput(outInputState);
// Store previous states
std::memcpy(m_keyPrevStates, m_keyStates, sizeof(m_keyStates));
std::memcpy(m_mouseButtonPrevStates, m_mouseButtonStates, sizeof(m_mouseButtonStates));
}
void DesktopPlatform::ResetInputFrameState(InputState& inputState)
{
inputState.ResetFrameStates();
m_scrollX = 0.0;
m_scrollY = 0.0;
}
void DesktopPlatform::UpdateKeyboardInput(InputState& outInputState)
{
GLFWwindow* window = m_graphicsContext->GetGLFWWindow();
// Read key states
m_keyStates[GLFW_KEY_W] = glfwGetKey(window, GLFW_KEY_W) == GLFW_PRESS;
m_keyStates[GLFW_KEY_S] = glfwGetKey(window, GLFW_KEY_S) == GLFW_PRESS;
m_keyStates[GLFW_KEY_A] = glfwGetKey(window, GLFW_KEY_A) == GLFW_PRESS;
m_keyStates[GLFW_KEY_D] = glfwGetKey(window, GLFW_KEY_D) == GLFW_PRESS;
m_keyStates[GLFW_KEY_E] = glfwGetKey(window, GLFW_KEY_E) == GLFW_PRESS;
m_keyStates[GLFW_KEY_Q] = glfwGetKey(window, GLFW_KEY_Q) == GLFW_PRESS;
m_keyStates[GLFW_KEY_SPACE] = glfwGetKey(window, GLFW_KEY_SPACE) == GLFW_PRESS;
m_keyStates[GLFW_KEY_LEFT_SHIFT] = glfwGetKey(window, GLFW_KEY_LEFT_SHIFT) == GLFW_PRESS;
m_keyStates[GLFW_KEY_LEFT_CONTROL] = glfwGetKey(window, GLFW_KEY_LEFT_CONTROL) == GLFW_PRESS;
m_keyStates[GLFW_KEY_ESCAPE] = glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS;
m_keyStates[GLFW_KEY_ENTER] = glfwGetKey(window, GLFW_KEY_ENTER) == GLFW_PRESS;
m_keyStates[GLFW_KEY_TAB] = glfwGetKey(window, GLFW_KEY_TAB) == GLFW_PRESS;
// Map to InputState actions
// Movement (digital)
outInputState.actionsHeld[static_cast<int>(InputAction::MoveForward)] = m_keyStates[GLFW_KEY_W];
outInputState.actionsHeld[static_cast<int>(InputAction::MoveBackward)] = m_keyStates[GLFW_KEY_S];
outInputState.actionsHeld[static_cast<int>(InputAction::MoveLeft)] = m_keyStates[GLFW_KEY_A];
outInputState.actionsHeld[static_cast<int>(InputAction::MoveRight)] = m_keyStates[GLFW_KEY_D];
outInputState.actionsHeld[static_cast<int>(InputAction::MoveUp)] = m_keyStates[GLFW_KEY_E];
outInputState.actionsHeld[static_cast<int>(InputAction::MoveDown)] = m_keyStates[GLFW_KEY_Q];
// Pressed this frame
outInputState.actions[static_cast<int>(InputAction::MoveForward)] =
m_keyStates[GLFW_KEY_W] && !m_keyPrevStates[GLFW_KEY_W];
outInputState.actions[static_cast<int>(InputAction::MoveBackward)] =
m_keyStates[GLFW_KEY_S] && !m_keyPrevStates[GLFW_KEY_S];
outInputState.actions[static_cast<int>(InputAction::MoveLeft)] =
m_keyStates[GLFW_KEY_A] && !m_keyPrevStates[GLFW_KEY_A];
outInputState.actions[static_cast<int>(InputAction::MoveRight)] =
m_keyStates[GLFW_KEY_D] && !m_keyPrevStates[GLFW_KEY_D];
// Menu
outInputState.actions[static_cast<int>(InputAction::MenuOpen)] =
m_keyStates[GLFW_KEY_ESCAPE] && !m_keyPrevStates[GLFW_KEY_ESCAPE];
outInputState.actionsHeld[static_cast<int>(InputAction::MenuOpen)] = m_keyStates[GLFW_KEY_ESCAPE];
outInputState.actions[static_cast<int>(InputAction::MenuConfirm)] =
m_keyStates[GLFW_KEY_ENTER] && !m_keyPrevStates[GLFW_KEY_ENTER];
// Other actions
outInputState.actionsHeld[static_cast<int>(InputAction::Jump)] = m_keyStates[GLFW_KEY_SPACE];
outInputState.actionsHeld[static_cast<int>(InputAction::Crouch)] = m_keyStates[GLFW_KEY_LEFT_SHIFT];
outInputState.actionsHeld[static_cast<int>(InputAction::Sprint)] = m_keyStates[GLFW_KEY_LEFT_CONTROL];
// Analog axes from digital input
outInputState.moveAxisX = 0.0f;
outInputState.moveAxisY = 0.0f;
if (m_keyStates[GLFW_KEY_W]) outInputState.moveAxisY += 1.0f;
if (m_keyStates[GLFW_KEY_S]) outInputState.moveAxisY -= 1.0f;
if (m_keyStates[GLFW_KEY_D]) outInputState.moveAxisX += 1.0f;
if (m_keyStates[GLFW_KEY_A]) outInputState.moveAxisX -= 1.0f;
// Device type
outInputState.deviceType = InputDeviceType::KeyboardMouse;
}
void DesktopPlatform::UpdateMouseInput(InputState& outInputState)
{
GLFWwindow* window = m_graphicsContext->GetGLFWWindow();
// Get mouse position
glfwGetCursorPos(window, &m_mouseX, &m_mouseY);
// Calculate delta
outInputState.pointerDeltaX = static_cast<float>(m_mouseX - m_prevMouseX);
outInputState.pointerDeltaY = static_cast<float>(m_mouseY - m_prevMouseY);
m_prevMouseX = m_mouseX;
m_prevMouseY = m_mouseY;
outInputState.pointerX = static_cast<float>(m_mouseX);
outInputState.pointerY = static_cast<float>(m_mouseY);
// Look axes (from mouse delta)
outInputState.lookAxisX = outInputState.pointerDeltaX;
outInputState.lookAxisY = outInputState.pointerDeltaY;
// Mouse buttons
m_mouseButtonStates[0] = glfwGetMouseButton(window, GLFW_MOUSE_BUTTON_LEFT) == GLFW_PRESS;
m_mouseButtonStates[1] = glfwGetMouseButton(window, GLFW_MOUSE_BUTTON_RIGHT) == GLFW_PRESS;
m_mouseButtonStates[2] = glfwGetMouseButton(window, GLFW_MOUSE_BUTTON_MIDDLE) == GLFW_PRESS;
// Map to actions
outInputState.actionsHeld[static_cast<int>(InputAction::Action1)] = m_mouseButtonStates[0];
outInputState.actionsHeld[static_cast<int>(InputAction::Action2)] = m_mouseButtonStates[1];
outInputState.actionsHeld[static_cast<int>(InputAction::Action3)] = m_mouseButtonStates[2];
// Pressed this frame
outInputState.actions[static_cast<int>(InputAction::Action1)] =
m_mouseButtonStates[0] && !m_mouseButtonPrevStates[0];
outInputState.actions[static_cast<int>(InputAction::Action2)] =
m_mouseButtonStates[1] && !m_mouseButtonPrevStates[1];
outInputState.actions[static_cast<int>(InputAction::Action3)] =
m_mouseButtonStates[2] && !m_mouseButtonPrevStates[2];
// Released this frame
outInputState.actionsReleased[static_cast<int>(InputAction::Action1)] =
!m_mouseButtonStates[0] && m_mouseButtonPrevStates[0];
outInputState.actionsReleased[static_cast<int>(InputAction::Action2)] =
!m_mouseButtonStates[1] && m_mouseButtonPrevStates[1];
// Scroll
outInputState.scrollDelta = static_cast<float>(m_scrollY);
// Scroll for cycling
if (m_scrollY > 0.1)
outInputState.actions[static_cast<int>(InputAction::CycleRight)] = true;
else if (m_scrollY < -0.1)
outInputState.actions[static_cast<int>(InputAction::CycleLeft)] = true;
// Pointer down
outInputState.pointerDown = m_mouseButtonStates[0];
}
void DesktopPlatform::UpdateGamepadInput(InputState& outInputState)
{
// Check if gamepad is connected
if (glfwJoystickPresent(GLFW_JOYSTICK_1))
{
GLFWgamepadstate state;
if (glfwGetGamepadState(GLFW_JOYSTICK_1, &state))
{
// Override device type
outInputState.deviceType = InputDeviceType::Gamepad;
// Axes
outInputState.moveAxisX = state.axes[GLFW_GAMEPAD_AXIS_LEFT_X];
outInputState.moveAxisY = state.axes[GLFW_GAMEPAD_AXIS_LEFT_Y];
outInputState.lookAxisX = state.axes[GLFW_GAMEPAD_AXIS_RIGHT_X];
outInputState.lookAxisY = state.axes[GLFW_GAMEPAD_AXIS_RIGHT_Y];
// Buttons
outInputState.actionsHeld[static_cast<int>(InputAction::Action1)] =
state.buttons[GLFW_GAMEPAD_BUTTON_A] == GLFW_PRESS;
outInputState.actionsHeld[static_cast<int>(InputAction::Action2)] =
state.buttons[GLFW_GAMEPAD_BUTTON_B] == GLFW_PRESS;
outInputState.actionsHeld[static_cast<int>(InputAction::Action3)] =
state.buttons[GLFW_GAMEPAD_BUTTON_X] == GLFW_PRESS;
outInputState.actionsHeld[static_cast<int>(InputAction::Action4)] =
state.buttons[GLFW_GAMEPAD_BUTTON_Y] == GLFW_PRESS;
outInputState.actionsHeld[static_cast<int>(InputAction::Jump)] =
state.buttons[GLFW_GAMEPAD_BUTTON_A] == GLFW_PRESS;
outInputState.actionsHeld[static_cast<int>(InputAction::MenuOpen)] =
state.buttons[GLFW_GAMEPAD_BUTTON_START] == GLFW_PRESS;
outInputState.actionsHeld[static_cast<int>(InputAction::MenuBack)] =
state.buttons[GLFW_GAMEPAD_BUTTON_BACK] == GLFW_PRESS;
outInputState.actionsHeld[static_cast<int>(InputAction::CycleLeft)] =
state.buttons[GLFW_GAMEPAD_BUTTON_LEFT_BUMPER] == GLFW_PRESS;
outInputState.actionsHeld[static_cast<int>(InputAction::CycleRight)] =
state.buttons[GLFW_GAMEPAD_BUTTON_RIGHT_BUMPER] == GLFW_PRESS;
}
}
}
IGraphicsContext* DesktopPlatform::GetGraphicsContext()
{
return m_graphicsContext.get();
}
const char* DesktopPlatform::GetUserDataPath() const
{
// Platform-specific user data paths
#if defined(PLATFORM_WINDOWS)
static const char* path = "./userdata"; // TODO: Use AppData
#elif defined(PLATFORM_MACOS)
static const char* path = "./userdata"; // TODO: Use ~/Library/Application Support
#else
static const char* path = "./userdata"; // TODO: Use ~/.local/share
#endif
return path;
}
const char* DesktopPlatform::GetAssetsPath() const
{
return "./assets";
}
const char* DesktopPlatform::GetPlatformName() const
{
return PLATFORM_NAME;
}
InputDeviceType DesktopPlatform::GetPrimaryInputDevice() const
{
return InputDeviceType::KeyboardMouse;
}
bool DesktopPlatform::HasFeature(const char* featureName) const
{
// Desktop has mouse, keyboard, filesystem, etc.
if (strcmp(featureName, "mouse") == 0) return true;
if (strcmp(featureName, "keyboard") == 0) return true;
if (strcmp(featureName, "gamepad") == 0) return true;
if (strcmp(featureName, "filesystem") == 0) return true;
return false;
}
}
+71
View File
@@ -0,0 +1,71 @@
#pragma once
#include <wv/platform/IPlatform.h>
#include "DesktopGraphicsContext.h"
#include <memory>
namespace WillowVox
{
/**
* Desktop Platform Implementation
*
* Implements IPlatform for Windows, Linux, and macOS.
* Uses GLFW for windowing and input.
*
* Input Mapping (Keyboard & Mouse):
* - WASD: Movement (Forward/Back/Left/Right)
* - Space/Shift: Up/Down
* - Mouse: Look
* - Left Click: Action1 (break block)
* - Right Click: Action2 (place block)
* - Middle Click: Action3 (pick block)
* - ESC: MenuOpen
* - E/Q: MoveUp/MoveDown
* - Mouse Scroll: CycleLeft/CycleRight
*/
class DesktopPlatform : public IPlatform
{
public:
DesktopPlatform();
~DesktopPlatform() override;
// IPlatform implementation
bool Initialize() override;
void Shutdown() override;
void PollInput(InputState& outInputState) override;
void ResetInputFrameState(InputState& inputState) override;
void ProcessEvents() override;
IGraphicsContext* GetGraphicsContext() override;
const char* GetUserDataPath() const override;
const char* GetAssetsPath() const override;
const char* GetPlatformName() const override;
InputDeviceType GetPrimaryInputDevice() const override;
bool HasFeature(const char* featureName) const override;
private:
std::unique_ptr<DesktopGraphicsContext> m_graphicsContext;
// Input tracking
bool m_keyStates[512] = {};
bool m_keyPrevStates[512] = {};
bool m_mouseButtonStates[8] = {};
bool m_mouseButtonPrevStates[8] = {};
double m_mouseX = 0.0;
double m_mouseY = 0.0;
double m_prevMouseX = 0.0;
double m_prevMouseY = 0.0;
double m_scrollX = 0.0;
double m_scrollY = 0.0;
// Helper methods
void UpdateKeyboardInput(InputState& outInputState);
void UpdateMouseInput(InputState& outInputState);
void UpdateGamepadInput(InputState& outInputState);
};
}
@@ -0,0 +1,43 @@
#pragma once
#include <wv/platform/IGraphicsContext.h>
namespace WillowVox
{
/**
* Nintendo GameCube Graphics Context (GX)
*
* GameCube uses the GX graphics API (libogc).
* This is a template implementation.
*/
class GameCubeGraphicsContext : public IGraphicsContext
{
public:
GameCubeGraphicsContext();
~GameCubeGraphicsContext() override;
bool Initialize(int width, int height, const char* title) override;
void Shutdown() override;
void BeginFrame() override;
void EndFrame() override;
void SwapBuffers() override;
void Clear(float r, float g, float b, float a) override;
void ClearDepth() override;
bool ShouldClose() const override;
void SetShouldClose(bool shouldClose) override;
void GetFramebufferSize(int& width, int& height) const override;
void GetWindowSize(int& width, int& height) const override;
void SetVSync(bool enabled) override;
bool IsVSyncEnabled() const override;
float GetTime() const override;
void SetBackgroundColor(float r, float g, float b, float a) override;
void SetViewport(int x, int y, int width, int height) override;
private:
bool m_initialized = false;
bool m_shouldClose = false;
int m_width = 640;
int m_height = 480;
float m_clearColor[4] = {0.1f, 0.1f, 0.1f, 1.0f};
};
}
+48
View File
@@ -0,0 +1,48 @@
#pragma once
#include <wv/platform/IPlatform.h>
#include <memory>
namespace WillowVox
{
class GameCubeGraphicsContext;
/**
* Nintendo GameCube Platform (devkitPPC Homebrew)
*
* Controller Mapping (GameCube Controller):
* - Control Stick: Movement
* - C-Stick: Look
* - A: Action1 / Jump
* - B: Action2
* - X: Action3
* - Y: Action4
* - L/R: Cycle items
* - Z: Crouch
* - Start: MenuOpen
*
* Graphics: GX (libogc)
* Input: PAD (libogc)
*/
class GameCubePlatform : public IPlatform
{
public:
GameCubePlatform();
~GameCubePlatform() override;
bool Initialize() override;
void Shutdown() override;
void PollInput(InputState& outInputState) override;
void ResetInputFrameState(InputState& inputState) override;
void ProcessEvents() override;
IGraphicsContext* GetGraphicsContext() override;
const char* GetUserDataPath() const override;
const char* GetAssetsPath() const override;
const char* GetPlatformName() const override;
InputDeviceType GetPrimaryInputDevice() const override;
bool HasFeature(const char* featureName) const override;
private:
std::unique_ptr<GameCubeGraphicsContext> m_graphicsContext;
};
}
@@ -0,0 +1,45 @@
#pragma once
#include <wv/platform/IGraphicsContext.h>
namespace WillowVox
{
/**
* Nintendo Switch Graphics Context (OpenGL)
*
* Switch homebrew supports OpenGL via mesa/deko3d or native NVN.
* This template uses OpenGL for simplicity.
*/
class SwitchGraphicsContext : public IGraphicsContext
{
public:
SwitchGraphicsContext();
~SwitchGraphicsContext() override;
bool Initialize(int width, int height, const char* title) override;
void Shutdown() override;
void BeginFrame() override;
void EndFrame() override;
void SwapBuffers() override;
void Clear(float r, float g, float b, float a) override;
void ClearDepth() override;
bool ShouldClose() const override;
void SetShouldClose(bool shouldClose) override;
void GetFramebufferSize(int& width, int& height) const override;
void GetWindowSize(int& width, int& height) const override;
void SetVSync(bool enabled) override;
bool IsVSyncEnabled() const override;
float GetTime() const override;
void SetBackgroundColor(float r, float g, float b, float a) override;
void SetViewport(int x, int y, int width, int height) override;
private:
bool m_initialized = false;
bool m_shouldClose = false;
bool m_vsyncEnabled = true;
int m_width = 1280;
int m_height = 720;
float m_clearColor[4] = {0.1f, 0.1f, 0.1f, 1.0f};
float m_startTime = 0.0f;
};
}
+184
View File
@@ -0,0 +1,184 @@
#include "SwitchPlatform.h"
#include "SwitchGraphicsContext.h"
#include <wv/Logger.h>
#ifdef PLATFORM_SWITCH
// libnx includes (devkitA64 homebrew SDK)
#include <switch.h>
namespace WillowVox
{
SwitchPlatform::SwitchPlatform()
{
}
SwitchPlatform::~SwitchPlatform()
{
Shutdown();
}
bool SwitchPlatform::Initialize()
{
Logger::EngineLog("Initializing Nintendo Switch Platform (libnx v4.0+)");
// Configure input for single player with standard controller styles
// HidNpadStyleSet_NpadStandard supports handheld, Joy-Con, and Pro Controller
padConfigureInput(1, HidNpadStyleSet_NpadStandard);
// Initialize the default gamepad (reads handheld mode and first connected controller)
padInitializeDefault(&m_pad);
// Initialize graphics
m_graphicsContext = std::make_unique<SwitchGraphicsContext>();
Logger::EngineLog("Switch Platform initialized");
return true;
}
void SwitchPlatform::Shutdown()
{
m_graphicsContext.reset();
Logger::EngineLog("Switch Platform shutdown");
}
void SwitchPlatform::ProcessEvents()
{
// Check if we should exit (e.g., user pressed home button)
if (!appletMainLoop())
{
m_graphicsContext->SetShouldClose(true);
}
}
void SwitchPlatform::PollInput(InputState& outInputState)
{
UpdateGamepadInput(outInputState);
outInputState.deviceType = InputDeviceType::SwitchController;
}
void SwitchPlatform::UpdateGamepadInput(InputState& outInputState)
{
m_prevButtons = m_buttons;
// Update pad state (must be called once per frame)
padUpdate(&m_pad);
// Get button states
m_buttons = padGetButtons(&m_pad);
u64 buttonsDown = padGetButtonsDown(&m_pad);
// Get analog stick positions
HidAnalogStickState lstick = padGetStickPos(&m_pad, 0); // Left stick
HidAnalogStickState rstick = padGetStickPos(&m_pad, 1); // Right stick
m_lstickX = lstick.x;
m_lstickY = lstick.y;
m_rstickX = rstick.x;
m_rstickY = rstick.y;
// Analog axes
outInputState.moveAxisX = NormalizeAxis(m_lstickX);
outInputState.moveAxisY = -NormalizeAxis(m_lstickY);
outInputState.lookAxisX = NormalizeAxis(m_rstickX) * 100.0f;
outInputState.lookAxisY = -NormalizeAxis(m_rstickY) * 100.0f;
// Button mapping (using libnx HidNpadButton constants)
bool btnA = (m_buttons & HidNpadButton_A) != 0;
bool btnB = (m_buttons & HidNpadButton_B) != 0;
bool btnX = (m_buttons & HidNpadButton_X) != 0;
bool btnY = (m_buttons & HidNpadButton_Y) != 0;
bool btnL = (m_buttons & HidNpadButton_L) != 0;
bool btnR = (m_buttons & HidNpadButton_R) != 0;
bool btnZL = (m_buttons & HidNpadButton_ZL) != 0;
bool btnZR = (m_buttons & HidNpadButton_ZR) != 0;
bool btnPlus = (m_buttons & HidNpadButton_Plus) != 0;
bool btnMinus = (m_buttons & HidNpadButton_Minus) != 0;
outInputState.actionsHeld[static_cast<int>(InputAction::Action1)] = btnA;
outInputState.actionsHeld[static_cast<int>(InputAction::Action2)] = btnB;
outInputState.actionsHeld[static_cast<int>(InputAction::Action3)] = btnX;
outInputState.actionsHeld[static_cast<int>(InputAction::Action4)] = btnY;
outInputState.actionsHeld[static_cast<int>(InputAction::Jump)] = btnA;
outInputState.actionsHeld[static_cast<int>(InputAction::Crouch)] = btnZL;
outInputState.actionsHeld[static_cast<int>(InputAction::Sprint)] = btnZR;
outInputState.actionsHeld[static_cast<int>(InputAction::CycleLeft)] = btnL;
outInputState.actionsHeld[static_cast<int>(InputAction::CycleRight)] = btnR;
outInputState.actionsHeld[static_cast<int>(InputAction::MenuOpen)] = btnPlus;
outInputState.actionsHeld[static_cast<int>(InputAction::MenuBack)] = btnMinus || btnB;
// Pressed this frame (using libnx HidNpadButton constants)
uint64_t pressed = m_buttons & ~m_prevButtons;
outInputState.actions[static_cast<int>(InputAction::Action1)] = (pressed & HidNpadButton_A) != 0;
outInputState.actions[static_cast<int>(InputAction::Action2)] = (pressed & HidNpadButton_B) != 0;
outInputState.actions[static_cast<int>(InputAction::MenuOpen)] = (pressed & HidNpadButton_Plus) != 0;
// Analog to digital
if (std::abs(outInputState.moveAxisY) > 0.3f)
{
if (outInputState.moveAxisY > 0.3f)
outInputState.actionsHeld[static_cast<int>(InputAction::MoveForward)] = true;
else
outInputState.actionsHeld[static_cast<int>(InputAction::MoveBackward)] = true;
}
if (std::abs(outInputState.moveAxisX) > 0.3f)
{
if (outInputState.moveAxisX > 0.3f)
outInputState.actionsHeld[static_cast<int>(InputAction::MoveRight)] = true;
else
outInputState.actionsHeld[static_cast<int>(InputAction::MoveLeft)] = true;
}
}
float SwitchPlatform::NormalizeAxis(int32_t value) const
{
// Switch analog sticks return values from -32768 to 32767
float normalized = static_cast<float>(value) / 32768.0f;
// Apply deadzone
const float deadzone = 0.15f;
if (std::abs(normalized) < deadzone)
return 0.0f;
return normalized;
}
void SwitchPlatform::ResetInputFrameState(InputState& inputState)
{
inputState.ResetFrameStates();
}
IGraphicsContext* SwitchPlatform::GetGraphicsContext()
{
return m_graphicsContext.get();
}
const char* SwitchPlatform::GetUserDataPath() const
{
return "/switch/WillowVox/userdata";
}
const char* SwitchPlatform::GetAssetsPath() const
{
return "/switch/WillowVox/assets";
}
const char* SwitchPlatform::GetPlatformName() const
{
return "Nintendo Switch";
}
InputDeviceType SwitchPlatform::GetPrimaryInputDevice() const
{
return InputDeviceType::SwitchController;
}
bool SwitchPlatform::HasFeature(const char* featureName) const
{
if (strcmp(featureName, "gamepad") == 0) return true;
if (strcmp(featureName, "filesystem") == 0) return true;
return false;
}
}
#endif // PLATFORM_SWITCH
+68
View File
@@ -0,0 +1,68 @@
#pragma once
#include <wv/platform/IPlatform.h>
#include <memory>
namespace WillowVox
{
class SwitchGraphicsContext;
/**
* Nintendo Switch Platform Implementation (libnx Homebrew)
*
* Controller Mapping (Joy-Con / Pro Controller):
* - Left Stick: Movement (MoveAxisX/Y)
* - Right Stick: Look (LookAxisX/Y)
* - A: Action1
* - B: Action2 / MenuBack
* - X: Action3
* - Y: Action4
* - L: CycleLeft
* - R: CycleRight
* - ZL: Crouch
* - ZR: Sprint
* - Plus (+): MenuOpen
* - Minus (-): MenuBack
*
* Uses: libnx (devkitA64), OpenGL for graphics, hidpad for input
*/
class SwitchPlatform : public IPlatform
{
public:
SwitchPlatform();
~SwitchPlatform() override;
bool Initialize() override;
void Shutdown() override;
void PollInput(InputState& outInputState) override;
void ResetInputFrameState(InputState& inputState) override;
void ProcessEvents() override;
IGraphicsContext* GetGraphicsContext() override;
const char* GetUserDataPath() const override;
const char* GetAssetsPath() const override;
const char* GetPlatformName() const override;
InputDeviceType GetPrimaryInputDevice() const override;
bool HasFeature(const char* featureName) const override;
private:
std::unique_ptr<SwitchGraphicsContext> m_graphicsContext;
// Pad state - using char array to avoid including switch.h in header
// Real type is PadState from libnx
alignas(8) char m_padStorage[0x400]; // Storage for PadState
// HID pad state (libnx)
uint64_t m_buttons = 0;
uint64_t m_prevButtons = 0;
int32_t m_lstickX = 0;
int32_t m_lstickY = 0;
int32_t m_rstickX = 0;
int32_t m_rstickY = 0;
void UpdateGamepadInput(InputState& outInputState);
float NormalizeAxis(int32_t value) const;
};
}
@@ -0,0 +1,43 @@
#pragma once
#include <wv/platform/IGraphicsContext.h>
namespace WillowVox
{
/**
* Nintendo Wii Graphics Context (GX)
*
* Wii uses the GX graphics API (libogc).
* This is a template implementation.
*/
class WiiGraphicsContext : public IGraphicsContext
{
public:
WiiGraphicsContext();
~WiiGraphicsContext() override;
bool Initialize(int width, int height, const char* title) override;
void Shutdown() override;
void BeginFrame() override;
void EndFrame() override;
void SwapBuffers() override;
void Clear(float r, float g, float b, float a) override;
void ClearDepth() override;
bool ShouldClose() const override;
void SetShouldClose(bool shouldClose) override;
void GetFramebufferSize(int& width, int& height) const override;
void GetWindowSize(int& width, int& height) const override;
void SetVSync(bool enabled) override;
bool IsVSyncEnabled() const override;
float GetTime() const override;
void SetBackgroundColor(float r, float g, float b, float a) override;
void SetViewport(int x, int y, int width, int height) override;
private:
bool m_initialized = false;
bool m_shouldClose = false;
int m_width = 640;
int m_height = 480;
float m_clearColor[4] = {0.1f, 0.1f, 0.1f, 1.0f};
};
}
+48
View File
@@ -0,0 +1,48 @@
#pragma once
#include <wv/platform/IPlatform.h>
#include <memory>
namespace WillowVox
{
class WiiGraphicsContext;
/**
* Nintendo Wii Platform (devkitPPC Homebrew)
*
* Controller Mapping (Wiimote + Nunchuk or Classic Controller):
* - Nunchuk Stick / Classic Left Stick: Movement
* - Wiimote IR / Classic Right Stick: Look
* - A / Classic A: Action1
* - B / Classic B: Action2
* - 1 / Classic X: Action3
* - 2 / Classic Y: Action4
* - Home: MenuOpen
* - Plus/Minus: Cycle items
*
* Graphics: GX (libogc)
* Input: WPAD (Wiimote) / PAD (GameCube controller)
*/
class WiiPlatform : public IPlatform
{
public:
WiiPlatform();
~WiiPlatform() override;
bool Initialize() override;
void Shutdown() override;
void PollInput(InputState& outInputState) override;
void ResetInputFrameState(InputState& inputState) override;
void ProcessEvents() override;
IGraphicsContext* GetGraphicsContext() override;
const char* GetUserDataPath() const override;
const char* GetAssetsPath() const override;
const char* GetPlatformName() const override;
InputDeviceType GetPrimaryInputDevice() const override;
bool HasFeature(const char* featureName) const override;
private:
std::unique_ptr<WiiGraphicsContext> m_graphicsContext;
// Wii-specific input handling
};
}
@@ -0,0 +1,43 @@
#pragma once
#include <wv/platform/IGraphicsContext.h>
namespace WillowVox
{
/**
* Nintendo Wii U Graphics Context (GX2)
*
* Wii U uses the GX2 graphics API (wut library).
* This is a template implementation.
*/
class WiiUGraphicsContext : public IGraphicsContext
{
public:
WiiUGraphicsContext();
~WiiUGraphicsContext() override;
bool Initialize(int width, int height, const char* title) override;
void Shutdown() override;
void BeginFrame() override;
void EndFrame() override;
void SwapBuffers() override;
void Clear(float r, float g, float b, float a) override;
void ClearDepth() override;
bool ShouldClose() const override;
void SetShouldClose(bool shouldClose) override;
void GetFramebufferSize(int& width, int& height) const override;
void GetWindowSize(int& width, int& height) const override;
void SetVSync(bool enabled) override;
bool IsVSyncEnabled() const override;
float GetTime() const override;
void SetBackgroundColor(float r, float g, float b, float a) override;
void SetViewport(int x, int y, int width, int height) override;
private:
bool m_initialized = false;
bool m_shouldClose = false;
int m_width = 1920;
int m_height = 1080;
float m_clearColor[4] = {0.1f, 0.1f, 0.1f, 1.0f};
};
}
+40
View File
@@ -0,0 +1,40 @@
#pragma once
#include <wv/platform/IPlatform.h>
#include <memory>
namespace WillowVox
{
class WiiUGraphicsContext;
/**
* Nintendo Wii U Platform (devkitPPC + wut Homebrew)
*
* Controller: Wii U Pro Controller / GamePad (similar to modern gamepad)
* Graphics: GX2
* Input: VPAD (GamePad) / WPAD (Wiimote) / KPAD (Pro Controller)
*
* Similar button mapping to Switch/modern controllers
*/
class WiiUPlatform : public IPlatform
{
public:
WiiUPlatform();
~WiiUPlatform() override;
bool Initialize() override;
void Shutdown() override;
void PollInput(InputState& outInputState) override;
void ResetInputFrameState(InputState& inputState) override;
void ProcessEvents() override;
IGraphicsContext* GetGraphicsContext() override;
const char* GetUserDataPath() const override;
const char* GetAssetsPath() const override;
const char* GetPlatformName() const override;
InputDeviceType GetPrimaryInputDevice() const override;
bool HasFeature(const char* featureName) const override;
private:
std::unique_ptr<WiiUGraphicsContext> m_graphicsContext;
};
}
+229
View File
@@ -0,0 +1,229 @@
#include "PS3GraphicsContext.h"
#include <wv/Logger.h>
#ifdef PLATFORM_PS3
#include <rsx/rsx.h>
#include <sysutil/video.h>
#include <PSGL/psgl.h>
#include <PSGL/psglu.h>
#include <sys/time.h>
namespace WillowVox
{
PS3GraphicsContext::PS3GraphicsContext()
{
struct timeval tv;
gettimeofday(&tv, nullptr);
m_startTime = tv.tv_sec + tv.tv_usec / 1000000.0f;
}
PS3GraphicsContext::~PS3GraphicsContext()
{
Shutdown();
}
bool PS3GraphicsContext::Initialize(int width, int height, const char* title)
{
m_width = width;
m_height = height;
Logger::EngineLog("Initializing PS3 Graphics (PSGL)");
// Initialize PSGL
PSGLinitOptions options = {
enable: PSGL_INIT_MAX_SPUS | PSGL_INIT_INITIALIZE_SPUS,
maxSPUs: 1,
initializeSPUs: false,
persistentMemorySize: 0,
transientMemorySize: 0,
errorConsole: 0,
fifoSize: 0,
hostMemorySize: 0
};
psglInit(&options);
// Get available video modes
videoState state;
videoConfiguration vconfig;
videoResolution resolution;
if (videoGetState(0, 0, &state) == 0 &&
videoGetResolution(state.displayMode.resolution, &resolution) == 0)
{
m_width = resolution.width;
m_height = resolution.height;
}
// Set video configuration
memset(&vconfig, 0, sizeof(videoConfiguration));
vconfig.resolution = VIDEORESOLUTION_1920x1080;
vconfig.format = VIDEO_BUFFER_FORMAT_XRGB;
vconfig.pitch = m_width * 4;
vconfig.aspect = VIDEO_ASPECT_16_9;
// Create PSGL device
PSGLdeviceParameters params;
params.enable = PSGL_DEVICE_PARAMETERS_COLOR_FORMAT |
PSGL_DEVICE_PARAMETERS_DEPTH_FORMAT |
PSGL_DEVICE_PARAMETERS_MULTISAMPLING_MODE;
params.colorFormat = GL_ARGB_SCE;
params.depthFormat = GL_DEPTH_COMPONENT24;
params.multisamplingMode = GL_MULTISAMPLING_NONE_SCE;
m_psglDevice = psglCreateDeviceExtended(&params);
if (!m_psglDevice)
{
Logger::EngineError("Failed to create PSGL device!");
return false;
}
// Create PSGL context
m_psglContext = psglCreateContext();
if (!m_psglContext)
{
Logger::EngineError("Failed to create PSGL context!");
psglDestroyDevice(m_psglDevice);
return false;
}
// Make context current
psglMakeCurrent(m_psglContext, m_psglDevice);
// Reset PSGL state
psglResetCurrentContext();
// Get actual framebuffer dimensions
GLuint fbWidth, fbHeight;
psglGetDeviceDimensions(m_psglDevice, &fbWidth, &fbHeight);
m_width = fbWidth;
m_height = fbHeight;
// Set viewport
glViewport(0, 0, m_width, m_height);
// Enable depth testing
glEnable(GL_DEPTH_TEST);
glDepthFunc(GL_LEQUAL);
// Enable culling
glEnable(GL_CULL_FACE);
glCullFace(GL_BACK);
// Enable blending
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
Logger::EngineLog("PS3 Graphics initialized (%dx%d)", m_width, m_height);
m_initialized = true;
return true;
}
void PS3GraphicsContext::Shutdown()
{
if (m_initialized)
{
if (m_psglContext)
{
psglDestroyContext(m_psglContext);
m_psglContext = nullptr;
}
if (m_psglDevice)
{
psglDestroyDevice(m_psglDevice);
m_psglDevice = nullptr;
}
psglExit();
m_initialized = false;
}
}
void PS3GraphicsContext::BeginFrame()
{
// Nothing specific for PSGL
}
void PS3GraphicsContext::EndFrame()
{
// Nothing specific for PSGL
}
void PS3GraphicsContext::SwapBuffers()
{
if (m_psglDevice)
{
psglSwap();
}
}
void PS3GraphicsContext::Clear(float r, float g, float b, float a)
{
glClearColor(r, g, b, a);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
}
void PS3GraphicsContext::ClearDepth()
{
glClear(GL_DEPTH_BUFFER_BIT);
}
bool PS3GraphicsContext::ShouldClose() const
{
return m_shouldClose;
}
void PS3GraphicsContext::SetShouldClose(bool shouldClose)
{
m_shouldClose = shouldClose;
}
void PS3GraphicsContext::GetFramebufferSize(int& width, int& height) const
{
width = m_width;
height = m_height;
}
void PS3GraphicsContext::GetWindowSize(int& width, int& height) const
{
width = m_width;
height = m_height;
}
void PS3GraphicsContext::SetVSync(bool enabled)
{
m_vsyncEnabled = enabled;
// PSGL VSync is typically controlled by psglSwap behavior
}
bool PS3GraphicsContext::IsVSyncEnabled() const
{
return m_vsyncEnabled;
}
float PS3GraphicsContext::GetTime() const
{
struct timeval tv;
gettimeofday(&tv, nullptr);
float currentTime = tv.tv_sec + tv.tv_usec / 1000000.0f;
return currentTime - m_startTime;
}
void PS3GraphicsContext::SetBackgroundColor(float r, float g, float b, float a)
{
m_clearColor[0] = r;
m_clearColor[1] = g;
m_clearColor[2] = b;
m_clearColor[3] = a;
}
void PS3GraphicsContext::SetViewport(int x, int y, int width, int height)
{
glViewport(x, y, width, height);
}
}
#endif // PLATFORM_PS3
+60
View File
@@ -0,0 +1,60 @@
#pragma once
#include <wv/platform/IGraphicsContext.h>
namespace WillowVox
{
/**
* PS3 Graphics Context (PSGL or libgcm)
*
* PS3 homebrew can use:
* - PSGL: OpenGL-like API (easier, but less performant)
* - libgcm: Low-level RSX graphics API (more complex, better performance)
*
* This implementation provides a template for either approach.
*/
class PS3GraphicsContext : public IGraphicsContext
{
public:
PS3GraphicsContext();
~PS3GraphicsContext() override;
bool Initialize(int width, int height, const char* title) override;
void Shutdown() override;
void BeginFrame() override;
void EndFrame() override;
void SwapBuffers() override;
void Clear(float r, float g, float b, float a) override;
void ClearDepth() override;
bool ShouldClose() const override;
void SetShouldClose(bool shouldClose) override;
void GetFramebufferSize(int& width, int& height) const override;
void GetWindowSize(int& width, int& height) const override;
void SetVSync(bool enabled) override;
bool IsVSyncEnabled() const override;
float GetTime() const override;
void SetBackgroundColor(float r, float g, float b, float a) override;
void SetViewport(int x, int y, int width, int height) override;
private:
bool m_initialized = false;
bool m_shouldClose = false;
bool m_vsyncEnabled = true;
float m_clearColor[4] = {0.1f, 0.1f, 0.1f, 1.0f};
int m_width = 1920;
int m_height = 1080;
float m_startTime = 0.0f;
// PS3-specific handles (void* for template)
void* m_psglDevice = nullptr;
void* m_psglContext = nullptr;
void* m_gcmContext = nullptr;
};
}
+223
View File
@@ -0,0 +1,223 @@
#include "PS3Platform.h"
#include "PS3GraphicsContext.h"
#include <wv/Logger.h>
#ifdef PLATFORM_PS3
// PSL1GHT includes (homebrew SDK)
#include <io/pad.h>
#include <sysutil/sysutil.h>
#include <sys/time.h>
// PSL1GHT pad button constants (from io/pad.h)
#define PAD_BTN_SELECT 0x0001
#define PAD_BTN_L3 0x0002
#define PAD_BTN_R3 0x0004
#define PAD_BTN_START 0x0008
#define PAD_BTN_UP 0x0010
#define PAD_BTN_RIGHT 0x0020
#define PAD_BTN_DOWN 0x0040
#define PAD_BTN_LEFT 0x0080
#define PAD_BTN_L2 0x0100
#define PAD_BTN_R2 0x0200
#define PAD_BTN_L1 0x0400
#define PAD_BTN_R1 0x0800
#define PAD_BTN_TRIANGLE 0x1000
#define PAD_BTN_CIRCLE 0x2000
#define PAD_BTN_CROSS 0x4000
#define PAD_BTN_SQUARE 0x8000
namespace WillowVox
{
PS3Platform::PS3Platform()
{
m_padState = {};
}
PS3Platform::~PS3Platform()
{
Shutdown();
}
bool PS3Platform::Initialize()
{
Logger::EngineLog("Initializing PS3 Platform (PSL1GHT Homebrew)");
// Initialize pad library (max 7 pads)
if (ioPadInit(7) != 0)
{
Logger::EngineError("Failed to initialize PS3 pad library!");
return false;
}
// Initialize graphics
m_graphicsContext = std::make_unique<PS3GraphicsContext>();
Logger::EngineLog("PS3 Platform initialized");
return true;
}
void PS3Platform::Shutdown()
{
m_graphicsContext.reset();
// Shutdown pad
ioPadEnd();
Logger::EngineLog("PS3 Platform shutdown");
}
void PS3Platform::ProcessEvents()
{
// PS3 homebrew doesn't have a traditional event loop
// Events are polled directly via ioPad, sysutil, etc.
// Handle system utility callbacks (important for XMB interaction)
sysUtilCheckCallback();
}
void PS3Platform::PollInput(InputState& outInputState)
{
UpdateGamepadInput(outInputState);
outInputState.deviceType = InputDeviceType::PSController;
}
void PS3Platform::UpdateGamepadInput(InputState& outInputState)
{
// Store previous button state
m_padState.prevButtons = m_padState.buttons;
// Read pad data from PSL1GHT
padInfo padinfo;
ioPadGetInfo(&padinfo);
// Reset to defaults
m_padState.buttons = 0;
m_padState.lstickX = 128;
m_padState.lstickY = 128;
m_padState.rstickX = 128;
m_padState.rstickY = 128;
// Check if pad 0 is connected
if (padinfo.status[0])
{
padData paddata;
if (ioPadGetData(0, &paddata) == 0 && paddata.len > 0)
{
// Button states (combine all buttons into one field)
m_padState.buttons = paddata.button[2] | (paddata.button[3] << 8);
// Analog stick values (0-255, center at 128)
m_padState.lstickX = paddata.ANA_L_H;
m_padState.lstickY = paddata.ANA_L_V;
m_padState.rstickX = paddata.ANA_R_H;
m_padState.rstickY = paddata.ANA_R_V;
}
}
// Analog sticks
outInputState.moveAxisX = NormalizeAxis(m_padState.lstickX);
outInputState.moveAxisY = -NormalizeAxis(m_padState.lstickY);
outInputState.lookAxisX = NormalizeAxis(m_padState.rstickX) * 100.0f;
outInputState.lookAxisY = -NormalizeAxis(m_padState.rstickY) * 100.0f;
// Button mapping
bool btnCross = (m_padState.buttons & PAD_BTN_CROSS) != 0;
bool btnCircle = (m_padState.buttons & PAD_BTN_CIRCLE) != 0;
bool btnSquare = (m_padState.buttons & PAD_BTN_SQUARE) != 0;
bool btnTriangle = (m_padState.buttons & PAD_BTN_TRIANGLE) != 0;
bool btnL1 = (m_padState.buttons & PAD_BTN_L1) != 0;
bool btnR1 = (m_padState.buttons & PAD_BTN_R1) != 0;
bool btnL2 = (m_padState.buttons & PAD_BTN_L2) != 0;
bool btnR2 = (m_padState.buttons & PAD_BTN_R2) != 0;
bool btnStart = (m_padState.buttons & PAD_BTN_START) != 0;
bool btnSelect = (m_padState.buttons & PAD_BTN_SELECT) != 0;
// Map to abstract actions
outInputState.actionsHeld[static_cast<int>(InputAction::Action1)] = btnCross;
outInputState.actionsHeld[static_cast<int>(InputAction::Action2)] = btnCircle;
outInputState.actionsHeld[static_cast<int>(InputAction::Action3)] = btnSquare;
outInputState.actionsHeld[static_cast<int>(InputAction::Action4)] = btnTriangle;
outInputState.actionsHeld[static_cast<int>(InputAction::Jump)] = btnCross;
outInputState.actionsHeld[static_cast<int>(InputAction::Crouch)] = btnL2;
outInputState.actionsHeld[static_cast<int>(InputAction::Sprint)] = btnR2;
outInputState.actionsHeld[static_cast<int>(InputAction::CycleLeft)] = btnL1;
outInputState.actionsHeld[static_cast<int>(InputAction::CycleRight)] = btnR1;
outInputState.actionsHeld[static_cast<int>(InputAction::MenuOpen)] = btnStart;
outInputState.actionsHeld[static_cast<int>(InputAction::MenuBack)] = btnSelect || btnCircle;
// Pressed this frame
uint16_t pressed = m_padState.buttons & ~m_padState.prevButtons;
outInputState.actions[static_cast<int>(InputAction::Action1)] = (pressed & PAD_BTN_CROSS) != 0;
outInputState.actions[static_cast<int>(InputAction::Action2)] = (pressed & PAD_BTN_CIRCLE) != 0;
outInputState.actions[static_cast<int>(InputAction::MenuOpen)] = (pressed & PAD_BTN_START) != 0;
// Analog to digital movement
if (std::abs(outInputState.moveAxisY) > 0.3f)
{
if (outInputState.moveAxisY > 0.3f)
outInputState.actionsHeld[static_cast<int>(InputAction::MoveForward)] = true;
else if (outInputState.moveAxisY < -0.3f)
outInputState.actionsHeld[static_cast<int>(InputAction::MoveBackward)] = true;
}
if (std::abs(outInputState.moveAxisX) > 0.3f)
{
if (outInputState.moveAxisX > 0.3f)
outInputState.actionsHeld[static_cast<int>(InputAction::MoveRight)] = true;
else if (outInputState.moveAxisX < -0.3f)
outInputState.actionsHeld[static_cast<int>(InputAction::MoveLeft)] = true;
}
}
float PS3Platform::NormalizeAxis(uint8_t value) const
{
// PS3 pad axes are 0-255, center at 128
float normalized = (static_cast<float>(value) - 128.0f) / 128.0f;
// Apply deadzone
const float deadzone = 0.15f;
if (std::abs(normalized) < deadzone)
return 0.0f;
return normalized;
}
void PS3Platform::ResetInputFrameState(InputState& inputState)
{
inputState.ResetFrameStates();
}
IGraphicsContext* PS3Platform::GetGraphicsContext()
{
return m_graphicsContext.get();
}
const char* PS3Platform::GetUserDataPath() const
{
return "/dev_hdd0/game/WVOX00001/USRDIR/userdata";
}
const char* PS3Platform::GetAssetsPath() const
{
return "/dev_hdd0/game/WVOX00001/USRDIR/assets";
}
const char* PS3Platform::GetPlatformName() const
{
return "PlayStation 3";
}
InputDeviceType PS3Platform::GetPrimaryInputDevice() const
{
return InputDeviceType::PSController;
}
bool PS3Platform::HasFeature(const char* featureName) const
{
if (strcmp(featureName, "gamepad") == 0) return true;
if (strcmp(featureName, "filesystem") == 0) return true;
return false;
}
}
#endif // PLATFORM_PS3
+65
View File
@@ -0,0 +1,65 @@
#pragma once
#include <wv/platform/IPlatform.h>
#include <memory>
namespace WillowVox
{
class PS3GraphicsContext;
/**
* PlayStation 3 Platform Implementation (PSL1GHT Homebrew)
*
* Controller Mapping (DualShock 3):
* - Left Stick: Movement (MoveAxisX/Y)
* - Right Stick: Look (LookAxisX/Y)
* - Cross (X): Action1 / Jump
* - Circle (O): Action2 / MenuBack
* - Square: Action3
* - Triangle: Action4
* - L1: CycleLeft
* - R1: CycleRight
* - L2: Crouch
* - R2: Sprint
* - Start: MenuOpen
* - Select: MenuBack
*
* Uses: libgcm or PSGL for graphics, libpad for input
*/
class PS3Platform : public IPlatform
{
public:
PS3Platform();
~PS3Platform() override;
bool Initialize() override;
void Shutdown() override;
void PollInput(InputState& outInputState) override;
void ResetInputFrameState(InputState& inputState) override;
void ProcessEvents() override;
IGraphicsContext* GetGraphicsContext() override;
const char* GetUserDataPath() const override;
const char* GetAssetsPath() const override;
const char* GetPlatformName() const override;
InputDeviceType GetPrimaryInputDevice() const override;
bool HasFeature(const char* featureName) const override;
private:
std::unique_ptr<PS3GraphicsContext> m_graphicsContext;
// PS3 pad state
struct PadState
{
uint16_t buttons;
uint16_t prevButtons;
uint8_t lstickX, lstickY;
uint8_t rstickX, rstickY;
} m_padState;
void UpdateGamepadInput(InputState& outInputState);
float NormalizeAxis(uint8_t value) const;
};
}
+43
View File
@@ -0,0 +1,43 @@
#pragma once
#include <wv/platform/IGraphicsContext.h>
namespace WillowVox
{
/**
* PS4 Graphics Context (GNM)
*
* PS4 uses GNM (low-level graphics API) via OpenOrbis SDK.
* This is a template implementation.
*/
class PS4GraphicsContext : public IGraphicsContext
{
public:
PS4GraphicsContext();
~PS4GraphicsContext() override;
bool Initialize(int width, int height, const char* title) override;
void Shutdown() override;
void BeginFrame() override;
void EndFrame() override;
void SwapBuffers() override;
void Clear(float r, float g, float b, float a) override;
void ClearDepth() override;
bool ShouldClose() const override;
void SetShouldClose(bool shouldClose) override;
void GetFramebufferSize(int& width, int& height) const override;
void GetWindowSize(int& width, int& height) const override;
void SetVSync(bool enabled) override;
bool IsVSyncEnabled() const override;
float GetTime() const override;
void SetBackgroundColor(float r, float g, float b, float a) override;
void SetViewport(int x, int y, int width, int height) override;
private:
bool m_initialized = false;
bool m_shouldClose = false;
int m_width = 1920;
int m_height = 1080;
float m_clearColor[4] = {0.1f, 0.1f, 0.1f, 1.0f};
};
}
+41
View File
@@ -0,0 +1,41 @@
#pragma once
#include <wv/platform/IPlatform.h>
#include <memory>
namespace WillowVox
{
class PS4GraphicsContext;
/**
* PlayStation 4 Platform (OpenOrbis Homebrew)
*
* Controller: DualShock 4 (same mapping as PS3)
* Graphics: GNM (low-level) or Gnm wrapper
* Input: libSceUserService + libScePad
*
* See PS3Platform for controller mapping (similar button layout)
*/
class PS4Platform : public IPlatform
{
public:
PS4Platform();
~PS4Platform() override;
bool Initialize() override;
void Shutdown() override;
void PollInput(InputState& outInputState) override;
void ResetInputFrameState(InputState& inputState) override;
void ProcessEvents() override;
IGraphicsContext* GetGraphicsContext() override;
const char* GetUserDataPath() const override;
const char* GetAssetsPath() const override;
const char* GetPlatformName() const override;
InputDeviceType GetPrimaryInputDevice() const override;
bool HasFeature(const char* featureName) const override;
private:
std::unique_ptr<PS4GraphicsContext> m_graphicsContext;
// PS4-specific pad handling (similar to PS3)
};
}
+95
View File
@@ -0,0 +1,95 @@
#include <wv/platform/IPlatform.h>
#include <wv/platform/IGraphicsContext.h>
#include <wv/platform/PlatformConfig.h>
// Include platform-specific implementations
#if defined(PLATFORM_DESKTOP)
#include "Desktop/DesktopPlatform.h"
#include "Desktop/DesktopGraphicsContext.h"
#elif defined(PLATFORM_ANDROID)
#include "Android/AndroidPlatform.h"
#include "Android/AndroidGraphicsContext.h"
#elif defined(PLATFORM_IOS)
#include "iOS/iOSPlatform.h"
#include "iOS/iOSGraphicsContext.h"
#elif defined(PLATFORM_PS3)
#include "PS3/PS3Platform.h"
#include "PS3/PS3GraphicsContext.h"
#elif defined(PLATFORM_PS4)
#include "PS4/PS4Platform.h"
#include "PS4/PS4GraphicsContext.h"
#elif defined(PLATFORM_WII)
#include "Nintendo/WiiPlatform.h"
#include "Nintendo/WiiGraphicsContext.h"
#elif defined(PLATFORM_GAMECUBE)
#include "Nintendo/GameCubePlatform.h"
#include "Nintendo/GameCubeGraphicsContext.h"
#elif defined(PLATFORM_WIIU)
#include "Nintendo/WiiUPlatform.h"
#include "Nintendo/WiiUGraphicsContext.h"
#elif defined(PLATFORM_SWITCH)
#include "Nintendo/SwitchPlatform.h"
#include "Nintendo/SwitchGraphicsContext.h"
#elif defined(PLATFORM_XBOX_SERIES_DEV)
#include "Xbox/XboxSeriesPlatform.h"
#include "Xbox/XboxSeriesGraphicsContext.h"
#endif
namespace WillowVox
{
IPlatform* PlatformFactory::CreatePlatform()
{
#if defined(PLATFORM_DESKTOP)
return new DesktopPlatform();
#elif defined(PLATFORM_ANDROID)
return new AndroidPlatform();
#elif defined(PLATFORM_IOS)
return new iOSPlatform();
#elif defined(PLATFORM_PS3)
return new PS3Platform();
#elif defined(PLATFORM_PS4)
return new PS4Platform();
#elif defined(PLATFORM_WII)
return new WiiPlatform();
#elif defined(PLATFORM_GAMECUBE)
return new GameCubePlatform();
#elif defined(PLATFORM_WIIU)
return new WiiUPlatform();
#elif defined(PLATFORM_SWITCH)
return new SwitchPlatform();
#elif defined(PLATFORM_XBOX_SERIES_DEV)
return new XboxSeriesPlatform();
#else
#error "Unsupported platform!"
return nullptr;
#endif
}
IGraphicsContext* GraphicsContextFactory::CreateGraphicsContext()
{
#if defined(PLATFORM_DESKTOP)
return new DesktopGraphicsContext();
#elif defined(PLATFORM_ANDROID)
return new AndroidGraphicsContext();
#elif defined(PLATFORM_IOS)
return new iOSGraphicsContext();
#elif defined(PLATFORM_PS3)
return new PS3GraphicsContext();
#elif defined(PLATFORM_PS4)
return new PS4GraphicsContext();
#elif defined(PLATFORM_WII)
return new WiiGraphicsContext();
#elif defined(PLATFORM_GAMECUBE)
return new GameCubeGraphicsContext();
#elif defined(PLATFORM_WIIU)
return new WiiUGraphicsContext();
#elif defined(PLATFORM_SWITCH)
return new SwitchGraphicsContext();
#elif defined(PLATFORM_XBOX_SERIES_DEV)
return new XboxSeriesGraphicsContext();
#else
#error "Unsupported platform!"
return nullptr;
#endif
}
}
@@ -0,0 +1,43 @@
#pragma once
#include <wv/platform/IGraphicsContext.h>
namespace WillowVox
{
/**
* Xbox Series X/S Graphics Context (DirectX 12)
*
* Xbox uses DirectX 12 via the GDK (Game Development Kit).
* This is a template implementation.
*/
class XboxSeriesGraphicsContext : public IGraphicsContext
{
public:
XboxSeriesGraphicsContext();
~XboxSeriesGraphicsContext() override;
bool Initialize(int width, int height, const char* title) override;
void Shutdown() override;
void BeginFrame() override;
void EndFrame() override;
void SwapBuffers() override;
void Clear(float r, float g, float b, float a) override;
void ClearDepth() override;
bool ShouldClose() const override;
void SetShouldClose(bool shouldClose) override;
void GetFramebufferSize(int& width, int& height) const override;
void GetWindowSize(int& width, int& height) const override;
void SetVSync(bool enabled) override;
bool IsVSyncEnabled() const override;
float GetTime() const override;
void SetBackgroundColor(float r, float g, float b, float a) override;
void SetViewport(int x, int y, int width, int height) override;
private:
bool m_initialized = false;
bool m_shouldClose = false;
int m_width = 1920;
int m_height = 1080;
float m_clearColor[4] = {0.1f, 0.1f, 0.1f, 1.0f};
};
}
+49
View File
@@ -0,0 +1,49 @@
#pragma once
#include <wv/platform/IPlatform.h>
#include <memory>
namespace WillowVox
{
class XboxSeriesGraphicsContext;
/**
* Xbox Series X/S Platform (Dev Mode / GDK)
*
* Controller: Xbox Series Controller (standard Xbox layout)
* - Left Stick: Movement
* - Right Stick: Look
* - A: Action1 / Jump
* - B: Action2 / MenuBack
* - X: Action3
* - Y: Action4
* - LB/RB: Cycle items
* - LT/RT: Crouch/Sprint
* - Menu (≡): MenuOpen
* - View (::): MenuBack
*
* Graphics: DirectX 12 (via GDK)
* Input: GameInput API or XInput
*/
class XboxSeriesPlatform : public IPlatform
{
public:
XboxSeriesPlatform();
~XboxSeriesPlatform() override;
bool Initialize() override;
void Shutdown() override;
void PollInput(InputState& outInputState) override;
void ResetInputFrameState(InputState& inputState) override;
void ProcessEvents() override;
IGraphicsContext* GetGraphicsContext() override;
const char* GetUserDataPath() const override;
const char* GetAssetsPath() const override;
const char* GetPlatformName() const override;
InputDeviceType GetPrimaryInputDevice() const override;
bool HasFeature(const char* featureName) const override;
private:
std::unique_ptr<XboxSeriesGraphicsContext> m_graphicsContext;
};
}
+43
View File
@@ -0,0 +1,43 @@
#pragma once
#include <wv/platform/IGraphicsContext.h>
namespace WillowVox
{
/**
* iOS Graphics Context (Metal or OpenGL ES)
*
* iOS can use Metal (preferred) or OpenGL ES for compatibility.
* This is a template implementation.
*/
class iOSGraphicsContext : public IGraphicsContext
{
public:
iOSGraphicsContext();
~iOSGraphicsContext() override;
bool Initialize(int width, int height, const char* title) override;
void Shutdown() override;
void BeginFrame() override;
void EndFrame() override;
void SwapBuffers() override;
void Clear(float r, float g, float b, float a) override;
void ClearDepth() override;
bool ShouldClose() const override;
void SetShouldClose(bool shouldClose) override;
void GetFramebufferSize(int& width, int& height) const override;
void GetWindowSize(int& width, int& height) const override;
void SetVSync(bool enabled) override;
bool IsVSyncEnabled() const override;
float GetTime() const override;
void SetBackgroundColor(float r, float g, float b, float a) override;
void SetViewport(int x, int y, int width, int height) override;
private:
bool m_initialized = false;
bool m_shouldClose = false;
int m_width = 0;
int m_height = 0;
float m_clearColor[4] = {0.1f, 0.1f, 0.1f, 1.0f};
};
}
+45
View File
@@ -0,0 +1,45 @@
#pragma once
#include <wv/platform/IPlatform.h>
#include <memory>
namespace WillowVox
{
class iOSGraphicsContext;
/**
* iOS Platform Implementation
*
* Virtual Touch Controls (similar to Android):
* - Left virtual joystick: Movement
* - Right side: Look control (drag)
* - Bottom right buttons: A (Action1), B (Action2), Jump
* - Top left: Menu button
*/
class iOSPlatform : public IPlatform
{
public:
iOSPlatform();
~iOSPlatform() override;
bool Initialize() override;
void Shutdown() override;
void PollInput(InputState& outInputState) override;
void ResetInputFrameState(InputState& inputState) override;
void ProcessEvents() override;
IGraphicsContext* GetGraphicsContext() override;
const char* GetUserDataPath() const override;
const char* GetAssetsPath() const override;
const char* GetPlatformName() const override;
InputDeviceType GetPrimaryInputDevice() const override;
bool HasFeature(const char* featureName) const override;
private:
std::unique_ptr<iOSGraphicsContext> m_graphicsContext;
// Touch input tracking (similar to Android)
// Implementation details omitted for brevity
};
}
-2
View File
@@ -39,8 +39,6 @@ namespace WillowVox
{
while (true)
{
std::this_thread::sleep_for(std::chrono::milliseconds(5));
std::function<void()> job;
{
/*m_mutexCondition.wait(lock, [this] {