fix: UIModule button interaction + JsonDataNode array children support
- Fix JsonDataNode::getChildReadOnly() to handle JSON array access by numeric index - Fix test_ui_showcase to use JSON array for children (matching test_single_button pattern) - Add visual test files: test_single_button, test_ui_showcase, test_sprite_debug - Clean up debug logging from SpritePass, SceneCollector, UIButton, BgfxDevice The root cause was that UITree couldn't access array children in JSON layouts. UIButton hover/click now works correctly in both test files. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
2f16ba0362
commit
5cef0e25b0
@ -26,6 +26,17 @@ struct Message {
|
|||||||
std::string topic;
|
std::string topic;
|
||||||
std::unique_ptr<IDataNode> data;
|
std::unique_ptr<IDataNode> data;
|
||||||
uint64_t timestamp;
|
uint64_t timestamp;
|
||||||
|
|
||||||
|
// Default constructor
|
||||||
|
Message() = default;
|
||||||
|
|
||||||
|
// Move constructor and assignment (unique_ptr is move-only)
|
||||||
|
Message(Message&&) = default;
|
||||||
|
Message& operator=(Message&&) = default;
|
||||||
|
|
||||||
|
// Delete copy (unique_ptr cannot be copied)
|
||||||
|
Message(const Message&) = delete;
|
||||||
|
Message& operator=(const Message&) = delete;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct IOHealth {
|
struct IOHealth {
|
||||||
|
|||||||
@ -70,6 +70,15 @@ private:
|
|||||||
std::chrono::high_resolution_clock::time_point lastBatch;
|
std::chrono::high_resolution_clock::time_point lastBatch;
|
||||||
std::unordered_map<std::string, Message> batchedMessages; // For replaceable messages
|
std::unordered_map<std::string, Message> batchedMessages; // For replaceable messages
|
||||||
std::vector<Message> accumulatedMessages; // For non-replaceable messages
|
std::vector<Message> accumulatedMessages; // For non-replaceable messages
|
||||||
|
|
||||||
|
// Default constructor
|
||||||
|
Subscription() = default;
|
||||||
|
|
||||||
|
// Move-only (Message contains unique_ptr)
|
||||||
|
Subscription(Subscription&&) = default;
|
||||||
|
Subscription& operator=(Subscription&&) = default;
|
||||||
|
Subscription(const Subscription&) = delete;
|
||||||
|
Subscription& operator=(const Subscription&) = delete;
|
||||||
};
|
};
|
||||||
|
|
||||||
std::vector<Subscription> highFreqSubscriptions;
|
std::vector<Subscription> highFreqSubscriptions;
|
||||||
|
|||||||
@ -177,61 +177,45 @@ void BgfxRendererModule::setConfiguration(const IDataNode& config, IIO* io, ITas
|
|||||||
}
|
}
|
||||||
|
|
||||||
void BgfxRendererModule::process(const IDataNode& input) {
|
void BgfxRendererModule::process(const IDataNode& input) {
|
||||||
// Validate device
|
|
||||||
if (!m_device) {
|
if (!m_device) {
|
||||||
m_logger->error("BgfxRenderer::process called but device is not initialized");
|
m_logger->error("Device not initialized");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Read deltaTime from input (provided by ModuleSystem)
|
// Reset frame allocator for this frame
|
||||||
|
if (m_frameAllocator) {
|
||||||
|
m_frameAllocator->reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Collect scene data from IIO messages and prepare frame packet
|
||||||
|
if (m_sceneCollector && m_renderGraph && m_frameAllocator && m_io) {
|
||||||
|
// Get delta time from input (or default to 16ms)
|
||||||
float deltaTime = static_cast<float>(input.getDouble("deltaTime", 0.016));
|
float deltaTime = static_cast<float>(input.getDouble("deltaTime", 0.016));
|
||||||
|
|
||||||
// Check for resize in input
|
// Collect all IIO messages for this frame
|
||||||
int newWidth = input.getInt("windowWidth", 0);
|
|
||||||
int newHeight = input.getInt("windowHeight", 0);
|
|
||||||
if (newWidth > 0 && newHeight > 0 &&
|
|
||||||
(static_cast<uint16_t>(newWidth) != m_width || static_cast<uint16_t>(newHeight) != m_height)) {
|
|
||||||
m_width = static_cast<uint16_t>(newWidth);
|
|
||||||
m_height = static_cast<uint16_t>(newHeight);
|
|
||||||
m_device->reset(m_width, m_height);
|
|
||||||
m_logger->info("Window resized to {}x{}", m_width, m_height);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 1. Collect IIO messages (pull-based)
|
|
||||||
if (m_sceneCollector && m_io) {
|
|
||||||
m_sceneCollector->collect(m_io, deltaTime);
|
m_sceneCollector->collect(m_io, deltaTime);
|
||||||
|
|
||||||
|
// Generate immutable FramePacket for render passes
|
||||||
|
FramePacket packet = m_sceneCollector->finalize(*m_frameAllocator);
|
||||||
|
|
||||||
|
// Apply view transform (projection matrix for 2D rendering)
|
||||||
|
m_device->setViewClear(0, packet.clearColor, 1.0f);
|
||||||
|
m_device->setViewRect(0, packet.mainView.viewportX, packet.mainView.viewportY,
|
||||||
|
packet.mainView.viewportW, packet.mainView.viewportH);
|
||||||
|
m_device->setViewTransform(0, packet.mainView.viewMatrix, packet.mainView.projMatrix);
|
||||||
|
|
||||||
|
// Execute render graph with collected scene data
|
||||||
|
m_renderGraph->execute(packet, *m_device);
|
||||||
|
|
||||||
|
// Clear staging buffers for next frame
|
||||||
|
m_sceneCollector->clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2. Build immutable FramePacket
|
// Present frame
|
||||||
if (!m_frameAllocator || !m_sceneCollector) {
|
|
||||||
m_logger->error("BgfxRenderer::process - frameAllocator or sceneCollector not initialized");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
m_frameAllocator->reset();
|
|
||||||
FramePacket frame = m_sceneCollector->finalize(*m_frameAllocator);
|
|
||||||
|
|
||||||
// 3. Set view clear color
|
|
||||||
m_device->setViewClear(0, frame.clearColor, 1.0f);
|
|
||||||
m_device->setViewRect(0, 0, 0, m_width, m_height);
|
|
||||||
m_device->setViewTransform(0, frame.mainView.viewMatrix, frame.mainView.projMatrix);
|
|
||||||
|
|
||||||
// 4. Execute render graph
|
|
||||||
m_renderGraph->execute(frame, *m_device);
|
|
||||||
|
|
||||||
// 5. Update and render debug overlay
|
|
||||||
if (m_debugOverlay) {
|
|
||||||
m_debugOverlay->update(deltaTime, static_cast<uint32_t>(frame.spriteCount), 1);
|
|
||||||
m_debugOverlay->render(m_width, m_height);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 6. Present
|
|
||||||
m_device->frame();
|
m_device->frame();
|
||||||
|
|
||||||
// 7. Cleanup for next frame
|
|
||||||
m_sceneCollector->clear();
|
|
||||||
m_frameCount++;
|
m_frameCount++;
|
||||||
}
|
}
|
||||||
|
|
||||||
void BgfxRendererModule::shutdown() {
|
void BgfxRendererModule::shutdown() {
|
||||||
m_logger->info("BgfxRenderer shutting down, {} frames rendered", m_frameCount);
|
m_logger->info("BgfxRenderer shutting down, {} frames rendered", m_frameCount);
|
||||||
|
|
||||||
@ -299,8 +283,11 @@ std::unique_ptr<IDataNode> BgfxRendererModule::getHealthStatus() {
|
|||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
// C Export (required for dlopen/LoadLibrary)
|
// C Export (required for dlopen/LoadLibrary)
|
||||||
|
// Skip when building as static library to avoid multiple definition errors
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
|
|
||||||
|
#ifndef GROVE_MODULE_STATIC
|
||||||
|
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
#define GROVE_MODULE_EXPORT __declspec(dllexport)
|
#define GROVE_MODULE_EXPORT __declspec(dllexport)
|
||||||
#else
|
#else
|
||||||
@ -318,3 +305,5 @@ GROVE_MODULE_EXPORT void destroyModule(grove::IModule* module) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#endif // GROVE_MODULE_STATIC
|
||||||
|
|||||||
@ -83,7 +83,13 @@ private:
|
|||||||
// C Export (required for dlopen)
|
// C Export (required for dlopen)
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
|
#define GROVE_MODULE_EXPORT __declspec(dllexport)
|
||||||
|
#else
|
||||||
|
#define GROVE_MODULE_EXPORT
|
||||||
|
#endif
|
||||||
|
|
||||||
extern "C" {
|
extern "C" {
|
||||||
grove::IModule* createModule();
|
GROVE_MODULE_EXPORT grove::IModule* createModule();
|
||||||
void destroyModule(grove::IModule* module);
|
GROVE_MODULE_EXPORT void destroyModule(grove::IModule* module);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -19,12 +19,15 @@ FetchContent_Declare(
|
|||||||
|
|
||||||
# CRITICAL: Disable bgfx multithreading BEFORE fetching to avoid TLS issues when running from a DLL on Windows
|
# CRITICAL: Disable bgfx multithreading BEFORE fetching to avoid TLS issues when running from a DLL on Windows
|
||||||
# Without this, bgfx::frame() crashes on Frame 1 due to render thread/DLL memory conflicts
|
# Without this, bgfx::frame() crashes on Frame 1 due to render thread/DLL memory conflicts
|
||||||
add_compile_definitions(BGFX_CONFIG_MULTITHREADED=0)
|
# Must use CMAKE_CXX_FLAGS to ensure the definition is passed to ALL compilation units including bgfx
|
||||||
|
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DBGFX_CONFIG_MULTITHREADED=0")
|
||||||
|
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -DBGFX_CONFIG_MULTITHREADED=0")
|
||||||
|
|
||||||
# Fix for MinGW GCC 15+ - glslang headers don't include <stdint.h>
|
# Fix for MinGW GCC 15+ - glslang headers don't include <stdint.h>
|
||||||
# Use stdint.h (C header, works in C++ too) to ensure uint32_t is defined
|
# Use stdint.h (C header, works in C++ too) to ensure uint32_t is defined
|
||||||
if(MINGW)
|
if(MINGW)
|
||||||
add_compile_options(-include stdint.h)
|
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -include stdint.h")
|
||||||
|
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -include stdint.h")
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
# bgfx options
|
# bgfx options
|
||||||
@ -36,6 +39,17 @@ set(BGFX_CUSTOM_TARGETS OFF CACHE BOOL "" FORCE)
|
|||||||
|
|
||||||
FetchContent_MakeAvailable(bgfx)
|
FetchContent_MakeAvailable(bgfx)
|
||||||
|
|
||||||
|
# CRITICAL: Force single-threaded mode on bgfx targets AFTER they're created
|
||||||
|
# This is necessary because CMAKE_CXX_FLAGS might not be applied to FetchContent targets
|
||||||
|
# Without this, bgfx::frame() crashes in DLL context due to render thread issues
|
||||||
|
if(TARGET bgfx)
|
||||||
|
target_compile_definitions(bgfx PRIVATE BGFX_CONFIG_MULTITHREADED=0)
|
||||||
|
message(STATUS "BGFX: Forcing BGFX_CONFIG_MULTITHREADED=0 on bgfx target")
|
||||||
|
endif()
|
||||||
|
if(TARGET bx)
|
||||||
|
target_compile_definitions(bx PRIVATE BGFX_CONFIG_MULTITHREADED=0)
|
||||||
|
endif()
|
||||||
|
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
# BgfxRenderer Shared Library
|
# BgfxRenderer Shared Library
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
@ -128,6 +142,89 @@ if(APPLE)
|
|||||||
)
|
)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
# ============================================================================
|
||||||
|
# BgfxRenderer STATIC Library (for Windows where DLL+bgfx doesn't work)
|
||||||
|
# ============================================================================
|
||||||
|
# On Windows, bgfx crashes when loaded from a DLL due to threading/TLS issues.
|
||||||
|
# This static library allows linking bgfx directly into the executable while
|
||||||
|
# still using the IModule interface.
|
||||||
|
|
||||||
|
add_library(BgfxRenderer_static STATIC
|
||||||
|
# Main module
|
||||||
|
BgfxRendererModule.cpp
|
||||||
|
|
||||||
|
# RHI
|
||||||
|
RHI/RHICommandBuffer.cpp
|
||||||
|
RHI/BgfxDevice.cpp
|
||||||
|
|
||||||
|
# Frame
|
||||||
|
Frame/FrameAllocator.cpp
|
||||||
|
|
||||||
|
# RenderGraph
|
||||||
|
RenderGraph/RenderGraph.cpp
|
||||||
|
|
||||||
|
# Shaders
|
||||||
|
Shaders/ShaderManager.cpp
|
||||||
|
|
||||||
|
# Passes
|
||||||
|
Passes/ClearPass.cpp
|
||||||
|
Passes/TilemapPass.cpp
|
||||||
|
Passes/SpritePass.cpp
|
||||||
|
Passes/TextPass.cpp
|
||||||
|
Passes/ParticlePass.cpp
|
||||||
|
Passes/DebugPass.cpp
|
||||||
|
|
||||||
|
# Text
|
||||||
|
Text/BitmapFont.cpp
|
||||||
|
|
||||||
|
# Scene
|
||||||
|
Scene/SceneCollector.cpp
|
||||||
|
|
||||||
|
# Resources
|
||||||
|
Resources/ResourceCache.cpp
|
||||||
|
Resources/TextureLoader.cpp
|
||||||
|
|
||||||
|
# Debug
|
||||||
|
Debug/DebugOverlay.cpp
|
||||||
|
)
|
||||||
|
|
||||||
|
target_include_directories(BgfxRenderer_static PUBLIC
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/../../include
|
||||||
|
${bgfx_SOURCE_DIR}/bimg/3rdparty
|
||||||
|
)
|
||||||
|
|
||||||
|
target_link_libraries(BgfxRenderer_static PUBLIC
|
||||||
|
GroveEngine::impl
|
||||||
|
bgfx
|
||||||
|
bx
|
||||||
|
spdlog::spdlog
|
||||||
|
)
|
||||||
|
|
||||||
|
target_compile_features(BgfxRenderer_static PRIVATE cxx_std_17)
|
||||||
|
|
||||||
|
# Mark as static build to skip C exports (avoids multiple definition errors)
|
||||||
|
target_compile_definitions(BgfxRenderer_static PRIVATE GROVE_MODULE_STATIC)
|
||||||
|
|
||||||
|
if(WIN32)
|
||||||
|
target_compile_definitions(BgfxRenderer_static PRIVATE
|
||||||
|
WIN32_LEAN_AND_MEAN
|
||||||
|
NOMINMAX
|
||||||
|
)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
if(UNIX AND NOT APPLE)
|
||||||
|
target_link_libraries(BgfxRenderer_static PUBLIC pthread dl X11 GL)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
if(APPLE)
|
||||||
|
target_link_libraries(BgfxRenderer_static PUBLIC
|
||||||
|
"-framework Cocoa"
|
||||||
|
"-framework QuartzCore"
|
||||||
|
"-framework Metal"
|
||||||
|
)
|
||||||
|
endif()
|
||||||
|
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
# Shader Compilation
|
# Shader Compilation
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
|
|||||||
@ -1,8 +1,27 @@
|
|||||||
#include "DebugPass.h"
|
#include "DebugPass.h"
|
||||||
#include "../RHI/RHIDevice.h"
|
#include "../RHI/RHIDevice.h"
|
||||||
|
#include "../Frame/FramePacket.h"
|
||||||
|
#include <vector>
|
||||||
|
#include <cstring>
|
||||||
|
#include <spdlog/spdlog.h>
|
||||||
|
|
||||||
namespace grove {
|
namespace grove {
|
||||||
|
|
||||||
|
// Vertex format: x, y, z, r, g, b, a (7 floats = 28 bytes)
|
||||||
|
struct DebugVertex {
|
||||||
|
float x, y, z;
|
||||||
|
float r, g, b, a;
|
||||||
|
};
|
||||||
|
static_assert(sizeof(DebugVertex) == 28, "DebugVertex must be 28 bytes");
|
||||||
|
|
||||||
|
// Helper to convert packed RGBA color to floats
|
||||||
|
static void unpackColor(uint32_t color, float& r, float& g, float& b, float& a) {
|
||||||
|
r = static_cast<float>((color >> 24) & 0xFF) / 255.0f;
|
||||||
|
g = static_cast<float>((color >> 16) & 0xFF) / 255.0f;
|
||||||
|
b = static_cast<float>((color >> 8) & 0xFF) / 255.0f;
|
||||||
|
a = static_cast<float>(color & 0xFF) / 255.0f;
|
||||||
|
}
|
||||||
|
|
||||||
DebugPass::DebugPass(rhi::ShaderHandle shader)
|
DebugPass::DebugPass(rhi::ShaderHandle shader)
|
||||||
: m_lineShader(shader)
|
: m_lineShader(shader)
|
||||||
{
|
{
|
||||||
@ -10,11 +29,14 @@ DebugPass::DebugPass(rhi::ShaderHandle shader)
|
|||||||
|
|
||||||
void DebugPass::setup(rhi::IRHIDevice& device) {
|
void DebugPass::setup(rhi::IRHIDevice& device) {
|
||||||
// Create dynamic vertex buffer for debug lines
|
// Create dynamic vertex buffer for debug lines
|
||||||
|
// Each line = 2 vertices, each rect = 4 lines = 8 vertices
|
||||||
|
// Buffer size: MAX_DEBUG_LINES * 2 vertices * 28 bytes
|
||||||
rhi::BufferDesc vbDesc;
|
rhi::BufferDesc vbDesc;
|
||||||
vbDesc.type = rhi::BufferDesc::Vertex;
|
vbDesc.type = rhi::BufferDesc::Vertex;
|
||||||
vbDesc.size = MAX_DEBUG_LINES * 2 * sizeof(float) * 6; // 2 verts per line, pos + color
|
vbDesc.size = MAX_DEBUG_LINES * 2 * sizeof(DebugVertex);
|
||||||
vbDesc.data = nullptr;
|
vbDesc.data = nullptr;
|
||||||
vbDesc.dynamic = true;
|
vbDesc.dynamic = true;
|
||||||
|
vbDesc.layout = rhi::BufferDesc::PosColor; // vec3 pos + vec4 color
|
||||||
m_lineVB = device.createBuffer(vbDesc);
|
m_lineVB = device.createBuffer(vbDesc);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -24,37 +46,93 @@ void DebugPass::shutdown(rhi::IRHIDevice& device) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void DebugPass::execute(const FramePacket& frame, rhi::IRHIDevice& device, rhi::RHICommandBuffer& cmd) {
|
void DebugPass::execute(const FramePacket& frame, rhi::IRHIDevice& device, rhi::RHICommandBuffer& cmd) {
|
||||||
|
static int logCounter = 0;
|
||||||
|
|
||||||
// Skip if no debug primitives
|
// Skip if no debug primitives
|
||||||
if (frame.debugLineCount == 0 && frame.debugRectCount == 0) {
|
if (frame.debugLineCount == 0 && frame.debugRectCount == 0) {
|
||||||
|
if (logCounter++ % 60 == 0) {
|
||||||
|
spdlog::debug("[DebugPass] No primitives (lines={}, rects={})", frame.debugLineCount, frame.debugRectCount);
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set render state for debug (no blending, no depth)
|
// Skip if shader is invalid
|
||||||
|
if (!m_lineShader.isValid()) {
|
||||||
|
spdlog::warn("[DebugPass] Shader invalid!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Log periodically
|
||||||
|
if (logCounter++ % 60 == 0) {
|
||||||
|
spdlog::info("[DebugPass] Drawing {} lines, {} rects (shader={})",
|
||||||
|
frame.debugLineCount, frame.debugRectCount, m_lineShader.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build vertex data for all debug primitives
|
||||||
|
std::vector<DebugVertex> vertices;
|
||||||
|
|
||||||
|
// Reserve space: lines (2 verts each) + rects (8 verts each for wireframe)
|
||||||
|
size_t totalVertices = frame.debugLineCount * 2 + frame.debugRectCount * 8;
|
||||||
|
vertices.reserve(totalVertices);
|
||||||
|
|
||||||
|
// Add line vertices
|
||||||
|
for (size_t i = 0; i < frame.debugLineCount; ++i) {
|
||||||
|
const DebugLine& line = frame.debugLines[i];
|
||||||
|
float r, g, b, a;
|
||||||
|
unpackColor(line.color, r, g, b, a);
|
||||||
|
|
||||||
|
// Vertex 1
|
||||||
|
vertices.push_back({line.x1, line.y1, 0.0f, r, g, b, a});
|
||||||
|
// Vertex 2
|
||||||
|
vertices.push_back({line.x2, line.y2, 0.0f, r, g, b, a});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add rect vertices as 4 lines (wireframe)
|
||||||
|
for (size_t i = 0; i < frame.debugRectCount; ++i) {
|
||||||
|
const DebugRect& rect = frame.debugRects[i];
|
||||||
|
float r, g, b, a;
|
||||||
|
unpackColor(rect.color, r, g, b, a);
|
||||||
|
|
||||||
|
float x1 = rect.x;
|
||||||
|
float y1 = rect.y;
|
||||||
|
float x2 = rect.x + rect.w;
|
||||||
|
float y2 = rect.y + rect.h;
|
||||||
|
|
||||||
|
// Line 1: top
|
||||||
|
vertices.push_back({x1, y1, 0.0f, r, g, b, a});
|
||||||
|
vertices.push_back({x2, y1, 0.0f, r, g, b, a});
|
||||||
|
// Line 2: right
|
||||||
|
vertices.push_back({x2, y1, 0.0f, r, g, b, a});
|
||||||
|
vertices.push_back({x2, y2, 0.0f, r, g, b, a});
|
||||||
|
// Line 3: bottom
|
||||||
|
vertices.push_back({x2, y2, 0.0f, r, g, b, a});
|
||||||
|
vertices.push_back({x1, y2, 0.0f, r, g, b, a});
|
||||||
|
// Line 4: left
|
||||||
|
vertices.push_back({x1, y2, 0.0f, r, g, b, a});
|
||||||
|
vertices.push_back({x1, y1, 0.0f, r, g, b, a});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (vertices.empty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update dynamic vertex buffer
|
||||||
|
device.updateBuffer(m_lineVB, vertices.data(),
|
||||||
|
static_cast<uint32_t>(vertices.size() * sizeof(DebugVertex)));
|
||||||
|
|
||||||
|
// Set render state for debug lines
|
||||||
rhi::RenderState state;
|
rhi::RenderState state;
|
||||||
state.blend = rhi::BlendMode::None;
|
state.blend = rhi::BlendMode::None;
|
||||||
state.cull = rhi::CullMode::None;
|
state.cull = rhi::CullMode::None;
|
||||||
|
state.primitive = rhi::PrimitiveType::Lines;
|
||||||
state.depthTest = false;
|
state.depthTest = false;
|
||||||
state.depthWrite = false;
|
state.depthWrite = false;
|
||||||
cmd.setState(state);
|
cmd.setState(state);
|
||||||
|
|
||||||
// Build vertex data for lines
|
// Draw all lines
|
||||||
// Each line needs 2 vertices with position (x, y, z) and color (r, g, b, a)
|
|
||||||
|
|
||||||
if (frame.debugLineCount > 0) {
|
|
||||||
// TODO: Build line vertex data from frame.debugLines and update buffer
|
|
||||||
// device.updateBuffer(m_lineVB, lineVertices, lineVertexDataSize);
|
|
||||||
cmd.setVertexBuffer(m_lineVB);
|
cmd.setVertexBuffer(m_lineVB);
|
||||||
cmd.draw(static_cast<uint32_t>(frame.debugLineCount * 2));
|
cmd.draw(static_cast<uint32_t>(vertices.size()));
|
||||||
cmd.submit(0, m_lineShader, 0);
|
cmd.submit(0, m_lineShader, 0);
|
||||||
}
|
|
||||||
|
|
||||||
// Rectangles are rendered as line loops or filled quads
|
|
||||||
// For now, just lines (wireframe)
|
|
||||||
if (frame.debugRectCount > 0) {
|
|
||||||
// Each rect = 4 lines = 8 vertices
|
|
||||||
// TODO: Build rect line data and draw
|
|
||||||
(void)device; // Will be used when implementing rect rendering
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace grove
|
} // namespace grove
|
||||||
|
|||||||
@ -4,6 +4,7 @@
|
|||||||
#include "../Resources/ResourceCache.h"
|
#include "../Resources/ResourceCache.h"
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
|
#include <spdlog/spdlog.h>
|
||||||
|
|
||||||
namespace grove {
|
namespace grove {
|
||||||
|
|
||||||
@ -58,15 +59,19 @@ void SpritePass::setup(rhi::IRHIDevice& device) {
|
|||||||
// Create texture sampler uniform (must match shader: s_texColor)
|
// Create texture sampler uniform (must match shader: s_texColor)
|
||||||
m_textureSampler = device.createUniform("s_texColor", 1);
|
m_textureSampler = device.createUniform("s_texColor", 1);
|
||||||
|
|
||||||
// Create default white 1x1 texture (used when no texture is bound)
|
// Create default white 4x4 texture (used when no texture is bound)
|
||||||
uint32_t whitePixel = 0xFFFFFFFF; // RGBA white
|
// Some drivers have issues with 1x1 textures
|
||||||
|
uint32_t whitePixels[16];
|
||||||
|
for (int i = 0; i < 16; ++i) whitePixels[i] = 0xFFFFFFFF; // RGBA white
|
||||||
rhi::TextureDesc texDesc;
|
rhi::TextureDesc texDesc;
|
||||||
texDesc.width = 1;
|
texDesc.width = 4;
|
||||||
texDesc.height = 1;
|
texDesc.height = 4;
|
||||||
texDesc.format = rhi::TextureDesc::RGBA8;
|
texDesc.format = rhi::TextureDesc::RGBA8;
|
||||||
texDesc.data = &whitePixel;
|
texDesc.data = whitePixels;
|
||||||
texDesc.dataSize = sizeof(whitePixel);
|
texDesc.dataSize = sizeof(whitePixels);
|
||||||
m_defaultTexture = device.createTexture(texDesc);
|
m_defaultTexture = device.createTexture(texDesc);
|
||||||
|
|
||||||
|
spdlog::info("SpritePass: defaultTexture valid={} (4x4 white)", m_defaultTexture.isValid());
|
||||||
}
|
}
|
||||||
|
|
||||||
void SpritePass::shutdown(rhi::IRHIDevice& device) {
|
void SpritePass::shutdown(rhi::IRHIDevice& device) {
|
||||||
@ -91,11 +96,9 @@ void SpritePass::flushBatch(rhi::IRHIDevice& device, rhi::RHICommandBuffer& cmd,
|
|||||||
}
|
}
|
||||||
|
|
||||||
void SpritePass::execute(const FramePacket& frame, rhi::IRHIDevice& device, rhi::RHICommandBuffer& cmd) {
|
void SpritePass::execute(const FramePacket& frame, rhi::IRHIDevice& device, rhi::RHICommandBuffer& cmd) {
|
||||||
if (frame.spriteCount == 0) {
|
if (frame.spriteCount == 0) return;
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set render state for sprites (alpha blending, no depth)
|
// Set render state ONCE (like TextPass does)
|
||||||
rhi::RenderState state;
|
rhi::RenderState state;
|
||||||
state.blend = rhi::BlendMode::Alpha;
|
state.blend = rhi::BlendMode::Alpha;
|
||||||
state.cull = rhi::CullMode::None;
|
state.cull = rhi::CullMode::None;
|
||||||
@ -103,90 +106,35 @@ void SpritePass::execute(const FramePacket& frame, rhi::IRHIDevice& device, rhi:
|
|||||||
state.depthWrite = false;
|
state.depthWrite = false;
|
||||||
cmd.setState(state);
|
cmd.setState(state);
|
||||||
|
|
||||||
// Build sorted indices by layer (primary) and textureId (secondary) for batching
|
// Sort sprites by layer for correct draw order
|
||||||
m_sortedIndices.clear();
|
m_sortedIndices.clear();
|
||||||
m_sortedIndices.reserve(frame.spriteCount);
|
m_sortedIndices.reserve(frame.spriteCount);
|
||||||
for (size_t i = 0; i < frame.spriteCount; ++i) {
|
for (size_t i = 0; i < frame.spriteCount; ++i) {
|
||||||
m_sortedIndices.push_back(static_cast<uint32_t>(i));
|
m_sortedIndices.push_back(static_cast<uint32_t>(i));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sort by layer first (ascending: layer 0 = background, rendered first)
|
|
||||||
// Then by textureId to batch sprites on the same layer
|
|
||||||
std::sort(m_sortedIndices.begin(), m_sortedIndices.end(),
|
std::sort(m_sortedIndices.begin(), m_sortedIndices.end(),
|
||||||
[&frame](uint32_t a, uint32_t b) {
|
[&frame](uint32_t a, uint32_t b) {
|
||||||
const SpriteInstance& sa = frame.sprites[a];
|
return frame.sprites[a].layer < frame.sprites[b].layer;
|
||||||
const SpriteInstance& sb = frame.sprites[b];
|
|
||||||
if (sa.layer != sb.layer) {
|
|
||||||
return sa.layer < sb.layer;
|
|
||||||
}
|
|
||||||
return sa.textureId < sb.textureId;
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Process sprites in batches by texture
|
// Copy sorted sprites to temporary buffer (like TextPass does with glyphs)
|
||||||
// Use transient buffers for proper multi-batch rendering
|
std::vector<SpriteInstance> sortedSprites;
|
||||||
uint32_t batchStart = 0;
|
sortedSprites.reserve(frame.spriteCount);
|
||||||
while (batchStart < frame.spriteCount) {
|
for (uint32_t idx : m_sortedIndices) {
|
||||||
// Find the end of current batch (same texture)
|
sortedSprites.push_back(frame.sprites[idx]);
|
||||||
uint16_t currentTexId = static_cast<uint16_t>(frame.sprites[m_sortedIndices[batchStart]].textureId);
|
|
||||||
uint32_t batchEnd = batchStart + 1;
|
|
||||||
|
|
||||||
while (batchEnd < frame.spriteCount) {
|
|
||||||
uint16_t nextTexId = static_cast<uint16_t>(frame.sprites[m_sortedIndices[batchEnd]].textureId);
|
|
||||||
if (nextTexId != currentTexId) {
|
|
||||||
break; // Texture changed, flush this batch
|
|
||||||
}
|
|
||||||
++batchEnd;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
uint32_t batchCount = batchEnd - batchStart;
|
// Update dynamic instance buffer with ALL sprites (like TextPass)
|
||||||
|
device.updateBuffer(m_instanceBuffer, sortedSprites.data(),
|
||||||
|
static_cast<uint32_t>(sortedSprites.size() * sizeof(SpriteInstance)));
|
||||||
|
|
||||||
// Resolve texture handle for this batch
|
// Set buffers and draw ALL sprites in ONE call (like TextPass)
|
||||||
rhi::TextureHandle batchTexture;
|
|
||||||
if (currentTexId == 0 || !m_resourceCache) {
|
|
||||||
batchTexture = m_activeTexture.isValid() ? m_activeTexture : m_defaultTexture;
|
|
||||||
} else {
|
|
||||||
batchTexture = m_resourceCache->getTextureById(currentTexId);
|
|
||||||
if (!batchTexture.isValid()) {
|
|
||||||
batchTexture = m_activeTexture.isValid() ? m_activeTexture : m_defaultTexture;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Allocate transient instance buffer for this batch
|
|
||||||
rhi::TransientInstanceBuffer transientBuffer = device.allocTransientInstanceBuffer(batchCount);
|
|
||||||
|
|
||||||
if (transientBuffer.isValid()) {
|
|
||||||
// Copy sprite data to transient buffer
|
|
||||||
SpriteInstance* dest = static_cast<SpriteInstance*>(transientBuffer.data);
|
|
||||||
for (uint32_t i = 0; i < batchCount; ++i) {
|
|
||||||
dest[i] = frame.sprites[m_sortedIndices[batchStart + i]];
|
|
||||||
}
|
|
||||||
|
|
||||||
// Re-set state for each batch to ensure clean state
|
|
||||||
cmd.setState(state);
|
|
||||||
|
|
||||||
// Set buffers and draw
|
|
||||||
cmd.setVertexBuffer(m_quadVB);
|
cmd.setVertexBuffer(m_quadVB);
|
||||||
cmd.setIndexBuffer(m_quadIB);
|
cmd.setIndexBuffer(m_quadIB);
|
||||||
cmd.setTransientInstanceBuffer(transientBuffer, 0, batchCount);
|
cmd.setInstanceBuffer(m_instanceBuffer, 0, static_cast<uint32_t>(sortedSprites.size()));
|
||||||
cmd.setTexture(0, batchTexture, m_textureSampler);
|
cmd.setTexture(0, m_defaultTexture, m_textureSampler);
|
||||||
cmd.drawInstanced(6, batchCount);
|
cmd.drawInstanced(6, static_cast<uint32_t>(sortedSprites.size()));
|
||||||
cmd.submit(0, m_shader, 0);
|
cmd.submit(0, m_shader, 0);
|
||||||
} else {
|
|
||||||
// Fallback: use dynamic buffer (may have issues with multiple batches)
|
|
||||||
// This should only happen if GPU runs out of transient memory
|
|
||||||
std::vector<SpriteInstance> batchData;
|
|
||||||
batchData.reserve(batchCount);
|
|
||||||
for (uint32_t i = 0; i < batchCount; ++i) {
|
|
||||||
batchData.push_back(frame.sprites[m_sortedIndices[batchStart + i]]);
|
|
||||||
}
|
|
||||||
device.updateBuffer(m_instanceBuffer, batchData.data(),
|
|
||||||
static_cast<uint32_t>(batchData.size() * sizeof(SpriteInstance)));
|
|
||||||
cmd.setInstanceBuffer(m_instanceBuffer, 0, batchCount);
|
|
||||||
flushBatch(device, cmd, batchTexture, batchCount);
|
|
||||||
}
|
|
||||||
|
|
||||||
batchStart = batchEnd;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace grove
|
} // namespace grove
|
||||||
|
|||||||
@ -121,9 +121,9 @@ void TilemapPass::execute(const FramePacket& frame, rhi::IRHIDevice& device, rhi
|
|||||||
size_t tileX = t % chunk.width;
|
size_t tileX = t % chunk.width;
|
||||||
size_t tileY = t / chunk.width;
|
size_t tileY = t / chunk.width;
|
||||||
|
|
||||||
// Calculate world position
|
// Calculate world position (add 0.5 tile offset because sprite shader centers quads)
|
||||||
float worldX = chunk.x + tileX * chunk.tileWidth;
|
float worldX = chunk.x + (tileX + 0.5f) * chunk.tileWidth;
|
||||||
float worldY = chunk.y + tileY * chunk.tileHeight;
|
float worldY = chunk.y + (tileY + 0.5f) * chunk.tileHeight;
|
||||||
|
|
||||||
// Calculate UV coords from tile index
|
// Calculate UV coords from tile index
|
||||||
// tileIndex-1 because 0 is empty, actual tiles start at 1
|
// tileIndex-1 because 0 is empty, actual tiles start at 1
|
||||||
|
|||||||
@ -30,9 +30,18 @@ public:
|
|||||||
m_width = width;
|
m_width = width;
|
||||||
m_height = height;
|
m_height = height;
|
||||||
|
|
||||||
|
// IMPORTANT: On Windows, we MUST call bgfx::renderFrame() before bgfx::init() to force
|
||||||
|
// single-threaded mode. This is required because:
|
||||||
|
// 1. In multi-threaded mode, bgfx starts a render thread that pumps Windows message queue
|
||||||
|
// 2. This conflicts with SDL_PollEvent which also pumps the message queue
|
||||||
|
// 3. The conflict causes crashes on frame 2
|
||||||
|
// With single-threaded mode, bgfx::frame() does all the work synchronously.
|
||||||
|
#ifdef _WIN32
|
||||||
|
// bgfx::renderFrame(); // Disabled - test_bgfx_minimal_win works without it
|
||||||
|
#endif
|
||||||
|
|
||||||
bgfx::Init init;
|
bgfx::Init init;
|
||||||
// Let bgfx auto-select the best renderer (D3D11 on Windows)
|
init.type = bgfx::RendererType::Direct3D11;
|
||||||
init.type = bgfx::RendererType::Count;
|
|
||||||
init.resolution.width = width;
|
init.resolution.width = width;
|
||||||
init.resolution.height = height;
|
init.resolution.height = height;
|
||||||
init.resolution.reset = BGFX_RESET_VSYNC;
|
init.resolution.reset = BGFX_RESET_VSYNC;
|
||||||
@ -49,8 +58,8 @@ public:
|
|||||||
// Don't enable it by default as it can cause issues on some platforms
|
// Don't enable it by default as it can cause issues on some platforms
|
||||||
// bgfx::setDebug(BGFX_DEBUG_TEXT);
|
// bgfx::setDebug(BGFX_DEBUG_TEXT);
|
||||||
|
|
||||||
// Set default view clear
|
// Set default view clear - BRIGHT RED for debugging
|
||||||
bgfx::setViewClear(0, BGFX_CLEAR_COLOR | BGFX_CLEAR_DEPTH, 0x303030FF, 1.0f, 0);
|
bgfx::setViewClear(0, BGFX_CLEAR_COLOR | BGFX_CLEAR_DEPTH, 0xFF0000FF, 1.0f, 0);
|
||||||
bgfx::setViewRect(0, 0, 0, width, height);
|
bgfx::setViewRect(0, 0, 0, width, height);
|
||||||
|
|
||||||
m_initialized = true;
|
m_initialized = true;
|
||||||
@ -327,6 +336,8 @@ public:
|
|||||||
bgfx::touch(0);
|
bgfx::touch(0);
|
||||||
|
|
||||||
// Present frame
|
// Present frame
|
||||||
|
// Note: bgfx must be linked statically on Windows to avoid TLS/threading crashes.
|
||||||
|
// Use BgfxRenderer_static library instead of BgfxRenderer DLL.
|
||||||
bgfx::frame();
|
bgfx::frame();
|
||||||
|
|
||||||
// Reset transient pool for next frame
|
// Reset transient pool for next frame
|
||||||
@ -386,6 +397,20 @@ public:
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Primitive type
|
||||||
|
switch (currentState.primitive) {
|
||||||
|
case PrimitiveType::Lines:
|
||||||
|
state |= BGFX_STATE_PT_LINES;
|
||||||
|
break;
|
||||||
|
case PrimitiveType::Points:
|
||||||
|
state |= BGFX_STATE_PT_POINTS;
|
||||||
|
break;
|
||||||
|
case PrimitiveType::Triangles:
|
||||||
|
default:
|
||||||
|
// Triangles is default, no flag needed
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
if (currentState.depthTest) {
|
if (currentState.depthTest) {
|
||||||
state |= BGFX_STATE_DEPTH_TEST_LESS;
|
state |= BGFX_STATE_DEPTH_TEST_LESS;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -65,9 +65,16 @@ enum class CullMode : uint8_t {
|
|||||||
CCW
|
CCW
|
||||||
};
|
};
|
||||||
|
|
||||||
|
enum class PrimitiveType : uint8_t {
|
||||||
|
Triangles,
|
||||||
|
Lines,
|
||||||
|
Points
|
||||||
|
};
|
||||||
|
|
||||||
struct RenderState {
|
struct RenderState {
|
||||||
BlendMode blend = BlendMode::Alpha;
|
BlendMode blend = BlendMode::Alpha;
|
||||||
CullMode cull = CullMode::None;
|
CullMode cull = CullMode::None;
|
||||||
|
PrimitiveType primitive = PrimitiveType::Triangles;
|
||||||
bool depthTest = false;
|
bool depthTest = false;
|
||||||
bool depthWrite = false;
|
bool depthWrite = false;
|
||||||
};
|
};
|
||||||
|
|||||||
@ -3,6 +3,7 @@
|
|||||||
#include "grove/IDataNode.h"
|
#include "grove/IDataNode.h"
|
||||||
#include "../Frame/FrameAllocator.h"
|
#include "../Frame/FrameAllocator.h"
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
|
#include <spdlog/spdlog.h>
|
||||||
|
|
||||||
namespace grove {
|
namespace grove {
|
||||||
|
|
||||||
@ -196,6 +197,7 @@ void SceneCollector::parseSprite(const IDataNode& data) {
|
|||||||
sprite.y = static_cast<float>(data.getDouble("y", 0.0));
|
sprite.y = static_cast<float>(data.getDouble("y", 0.0));
|
||||||
sprite.scaleX = static_cast<float>(data.getDouble("scaleX", 1.0));
|
sprite.scaleX = static_cast<float>(data.getDouble("scaleX", 1.0));
|
||||||
sprite.scaleY = static_cast<float>(data.getDouble("scaleY", 1.0));
|
sprite.scaleY = static_cast<float>(data.getDouble("scaleY", 1.0));
|
||||||
|
|
||||||
// i_data1
|
// i_data1
|
||||||
sprite.rotation = static_cast<float>(data.getDouble("rotation", 0.0));
|
sprite.rotation = static_cast<float>(data.getDouble("rotation", 0.0));
|
||||||
sprite.u0 = static_cast<float>(data.getDouble("u0", 0.0));
|
sprite.u0 = static_cast<float>(data.getDouble("u0", 0.0));
|
||||||
@ -366,8 +368,13 @@ void SceneCollector::parseDebugRect(const IDataNode& data) {
|
|||||||
DebugRect rect;
|
DebugRect rect;
|
||||||
rect.x = static_cast<float>(data.getDouble("x", 0.0));
|
rect.x = static_cast<float>(data.getDouble("x", 0.0));
|
||||||
rect.y = static_cast<float>(data.getDouble("y", 0.0));
|
rect.y = static_cast<float>(data.getDouble("y", 0.0));
|
||||||
rect.w = static_cast<float>(data.getDouble("w", 0.0));
|
// Accept both "w"/"h" and "width"/"height" for convenience
|
||||||
rect.h = static_cast<float>(data.getDouble("h", 0.0));
|
double w = data.getDouble("w", 0.0);
|
||||||
|
if (w == 0.0) w = data.getDouble("width", 0.0);
|
||||||
|
double h = data.getDouble("h", 0.0);
|
||||||
|
if (h == 0.0) h = data.getDouble("height", 0.0);
|
||||||
|
rect.w = static_cast<float>(w);
|
||||||
|
rect.h = static_cast<float>(h);
|
||||||
rect.color = static_cast<uint32_t>(data.getInt("color", 0x00FF00FF));
|
rect.color = static_cast<uint32_t>(data.getInt("color", 0x00FF00FF));
|
||||||
rect.filled = data.getBool("filled", false);
|
rect.filled = data.getBool("filled", false);
|
||||||
|
|
||||||
|
|||||||
@ -124,6 +124,11 @@ void ShaderManager::loadSpriteShader(rhi::IRHIDevice& device, const std::string&
|
|||||||
vsSize = sizeof(vs_sprite_mtl);
|
vsSize = sizeof(vs_sprite_mtl);
|
||||||
fsData = fs_sprite_mtl;
|
fsData = fs_sprite_mtl;
|
||||||
fsSize = sizeof(fs_sprite_mtl);
|
fsSize = sizeof(fs_sprite_mtl);
|
||||||
|
} else if (rendererName == "Direct3D 11" || rendererName == "Direct3D 12") {
|
||||||
|
vsData = vs_sprite_dx11;
|
||||||
|
vsSize = sizeof(vs_sprite_dx11);
|
||||||
|
fsData = fs_sprite_dx11;
|
||||||
|
fsSize = sizeof(fs_sprite_dx11);
|
||||||
} else {
|
} else {
|
||||||
// Fallback to Vulkan (most common in WSL2)
|
// Fallback to Vulkan (most common in WSL2)
|
||||||
vsData = vs_sprite_spv;
|
vsData = vs_sprite_spv;
|
||||||
|
|||||||
@ -155,3 +155,38 @@ static const uint8_t fs_sprite_mtl[] = {
|
|||||||
0x0a, 0x7d, 0x0a, 0x0a, 0x00, 0x00, 0x20, 0x00
|
0x0a, 0x7d, 0x0a, 0x0a, 0x00, 0x00, 0x20, 0x00
|
||||||
};
|
};
|
||||||
static const unsigned int fs_sprite_mtl_len = sizeof(fs_sprite_mtl);
|
static const unsigned int fs_sprite_mtl_len = sizeof(fs_sprite_mtl);
|
||||||
|
|
||||||
|
// D3D11 bytecode - compiled with shaderc for Shader Model 5.0
|
||||||
|
static const uint8_t fs_sprite_dx11[464] =
|
||||||
|
{
|
||||||
|
0x46, 0x53, 0x48, 0x0b, 0x01, 0x83, 0xf2, 0xe1, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x0a, 0x73, // FSH............s
|
||||||
|
0x5f, 0x74, 0x65, 0x78, 0x43, 0x6f, 0x6c, 0x6f, 0x72, 0x30, 0x01, 0x00, 0x00, 0x01, 0x00, 0x00, // _texColor0......
|
||||||
|
0x00, 0x00, 0x00, 0x0a, 0x73, 0x5f, 0x74, 0x65, 0x78, 0x43, 0x6f, 0x6c, 0x6f, 0x72, 0x30, 0x01, // ....s_texColor0.
|
||||||
|
0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x90, 0x01, 0x00, 0x00, 0x44, 0x58, 0x42, 0x43, // ............DXBC
|
||||||
|
0x20, 0x19, 0x9e, 0xef, 0x46, 0xd7, 0xd2, 0x3f, 0xe3, 0xed, 0x65, 0x99, 0xfb, 0x76, 0x3c, 0xf8, // ...F..?..e..v<.
|
||||||
|
0x01, 0x00, 0x00, 0x00, 0x90, 0x01, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x2c, 0x00, 0x00, 0x00, // ............,...
|
||||||
|
0xa0, 0x00, 0x00, 0x00, 0xd4, 0x00, 0x00, 0x00, 0x49, 0x53, 0x47, 0x4e, 0x6c, 0x00, 0x00, 0x00, // ........ISGNl...
|
||||||
|
0x03, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x50, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // ........P.......
|
||||||
|
0x01, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, // ................
|
||||||
|
0x5c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, // ................
|
||||||
|
0x01, 0x00, 0x00, 0x00, 0x0f, 0x0f, 0x00, 0x00, 0x62, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // ........b.......
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x03, 0x03, 0x00, 0x00, // ................
|
||||||
|
0x53, 0x56, 0x5f, 0x50, 0x4f, 0x53, 0x49, 0x54, 0x49, 0x4f, 0x4e, 0x00, 0x43, 0x4f, 0x4c, 0x4f, // SV_POSITION.COLO
|
||||||
|
0x52, 0x00, 0x54, 0x45, 0x58, 0x43, 0x4f, 0x4f, 0x52, 0x44, 0x00, 0xab, 0x4f, 0x53, 0x47, 0x4e, // R.TEXCOORD..OSGN
|
||||||
|
0x2c, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, // ,........... ...
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // ................
|
||||||
|
0x0f, 0x00, 0x00, 0x00, 0x53, 0x56, 0x5f, 0x54, 0x41, 0x52, 0x47, 0x45, 0x54, 0x00, 0xab, 0xab, // ....SV_TARGET...
|
||||||
|
0x53, 0x48, 0x45, 0x58, 0xb4, 0x00, 0x00, 0x00, 0x50, 0x00, 0x00, 0x00, 0x2d, 0x00, 0x00, 0x00, // SHEX....P...-...
|
||||||
|
0x6a, 0x88, 0x00, 0x01, 0x5a, 0x00, 0x00, 0x03, 0x00, 0x60, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, // j...Z....`......
|
||||||
|
0x58, 0x18, 0x00, 0x04, 0x00, 0x70, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x55, 0x55, 0x00, 0x00, // X....p......UU..
|
||||||
|
0x62, 0x10, 0x00, 0x03, 0xf2, 0x10, 0x10, 0x00, 0x01, 0x00, 0x00, 0x00, 0x62, 0x10, 0x00, 0x03, // b...........b...
|
||||||
|
0x32, 0x10, 0x10, 0x00, 0x02, 0x00, 0x00, 0x00, 0x65, 0x00, 0x00, 0x03, 0xf2, 0x20, 0x10, 0x00, // 2.......e.... ..
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x68, 0x00, 0x00, 0x02, 0x01, 0x00, 0x00, 0x00, 0x45, 0x00, 0x00, 0x8b, // ....h.......E...
|
||||||
|
0xc2, 0x00, 0x00, 0x80, 0x43, 0x55, 0x15, 0x00, 0xf2, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, // ....CU..........
|
||||||
|
0x46, 0x10, 0x10, 0x00, 0x02, 0x00, 0x00, 0x00, 0x46, 0x7e, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, // F.......F~......
|
||||||
|
0x00, 0x60, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x05, 0xf2, 0x00, 0x10, 0x00, // .`......6.......
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x46, 0x0e, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x38, 0x00, 0x00, 0x07, // ....F.......8...
|
||||||
|
0xf2, 0x20, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46, 0x0e, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, // . ......F.......
|
||||||
|
0x46, 0x1e, 0x10, 0x00, 0x01, 0x00, 0x00, 0x00, 0x3e, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, // F.......>.......
|
||||||
|
};
|
||||||
|
static const unsigned int fs_sprite_dx11_len = sizeof(fs_sprite_dx11);
|
||||||
|
|||||||
@ -373,3 +373,112 @@ static const uint8_t vs_sprite_mtl[] = {
|
|||||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x40, 0x00
|
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x40, 0x00
|
||||||
};
|
};
|
||||||
static const unsigned int vs_sprite_mtl_len = sizeof(vs_sprite_mtl);
|
static const unsigned int vs_sprite_mtl_len = sizeof(vs_sprite_mtl);
|
||||||
|
|
||||||
|
// D3D11 bytecode - compiled with shaderc for Shader Model 5.0
|
||||||
|
static const uint8_t vs_sprite_dx11[1633] =
|
||||||
|
{
|
||||||
|
0x56, 0x53, 0x48, 0x0b, 0x00, 0x00, 0x00, 0x00, 0x01, 0x83, 0xf2, 0xe1, 0x01, 0x00, 0x0a, 0x75, // VSH............u
|
||||||
|
0x5f, 0x76, 0x69, 0x65, 0x77, 0x50, 0x72, 0x6f, 0x6a, 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, // _viewProj.......
|
||||||
|
0x00, 0x00, 0x00, 0x28, 0x06, 0x00, 0x00, 0x44, 0x58, 0x42, 0x43, 0xdb, 0xbf, 0xa7, 0xd1, 0xaa, // ...(...DXBC.....
|
||||||
|
0x42, 0xf6, 0x9b, 0xee, 0xb1, 0xb3, 0xd0, 0x4c, 0xe4, 0xb7, 0x78, 0x01, 0x00, 0x00, 0x00, 0x28, // B......L..x....(
|
||||||
|
0x06, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x2c, 0x00, 0x00, 0x00, 0xfc, 0x00, 0x00, 0x00, 0x70, // .......,.......p
|
||||||
|
0x01, 0x00, 0x00, 0x49, 0x53, 0x47, 0x4e, 0xc8, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x08, // ...ISGN.........
|
||||||
|
0x00, 0x00, 0x00, 0xb0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, // ................
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0x0f, 0x00, 0x00, 0xb6, 0x00, 0x00, 0x00, 0x00, // ................
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x07, // ................
|
||||||
|
0x03, 0x00, 0x00, 0xbf, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, // ................
|
||||||
|
0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x0f, 0x0f, 0x00, 0x00, 0xbf, 0x00, 0x00, 0x00, 0x06, // ................
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x0f, // ................
|
||||||
|
0x0f, 0x00, 0x00, 0xbf, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, // ................
|
||||||
|
0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x0f, 0x01, 0x00, 0x00, 0xbf, 0x00, 0x00, 0x00, 0x04, // ................
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x0f, // ................
|
||||||
|
0x00, 0x00, 0x00, 0xbf, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, // ................
|
||||||
|
0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x0f, 0x0f, 0x00, 0x00, 0x43, 0x4f, 0x4c, 0x4f, 0x52, // ...........COLOR
|
||||||
|
0x00, 0x50, 0x4f, 0x53, 0x49, 0x54, 0x49, 0x4f, 0x4e, 0x00, 0x54, 0x45, 0x58, 0x43, 0x4f, 0x4f, // .POSITION.TEXCOO
|
||||||
|
0x52, 0x44, 0x00, 0x4f, 0x53, 0x47, 0x4e, 0x6c, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x08, // RD.OSGNl........
|
||||||
|
0x00, 0x00, 0x00, 0x50, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x03, // ...P............
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x5c, 0x00, 0x00, 0x00, 0x00, // ................
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x0f, // ................
|
||||||
|
0x00, 0x00, 0x00, 0x62, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, // ...b............
|
||||||
|
0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x03, 0x0c, 0x00, 0x00, 0x53, 0x56, 0x5f, 0x50, 0x4f, // ...........SV_PO
|
||||||
|
0x53, 0x49, 0x54, 0x49, 0x4f, 0x4e, 0x00, 0x43, 0x4f, 0x4c, 0x4f, 0x52, 0x00, 0x54, 0x45, 0x58, // SITION.COLOR.TEX
|
||||||
|
0x43, 0x4f, 0x4f, 0x52, 0x44, 0x00, 0xab, 0x53, 0x48, 0x45, 0x58, 0xb0, 0x04, 0x00, 0x00, 0x50, // COORD..SHEX....P
|
||||||
|
0x00, 0x01, 0x00, 0x2c, 0x01, 0x00, 0x00, 0x6a, 0x88, 0x00, 0x01, 0x59, 0x00, 0x00, 0x04, 0x46, // ...,...j...Y...F
|
||||||
|
0x8e, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x5f, 0x00, 0x00, 0x03, 0xf2, // . ........._....
|
||||||
|
0x10, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x5f, 0x00, 0x00, 0x03, 0x32, 0x10, 0x10, 0x00, 0x01, // ......._...2....
|
||||||
|
0x00, 0x00, 0x00, 0x5f, 0x00, 0x00, 0x03, 0xf2, 0x10, 0x10, 0x00, 0x02, 0x00, 0x00, 0x00, 0x5f, // ..._..........._
|
||||||
|
0x00, 0x00, 0x03, 0xf2, 0x10, 0x10, 0x00, 0x03, 0x00, 0x00, 0x00, 0x5f, 0x00, 0x00, 0x03, 0x12, // ..........._....
|
||||||
|
0x10, 0x10, 0x00, 0x04, 0x00, 0x00, 0x00, 0x5f, 0x00, 0x00, 0x03, 0xf2, 0x10, 0x10, 0x00, 0x06, // ......._........
|
||||||
|
0x00, 0x00, 0x00, 0x67, 0x00, 0x00, 0x04, 0xf2, 0x20, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, // ...g.... .......
|
||||||
|
0x00, 0x00, 0x00, 0x65, 0x00, 0x00, 0x03, 0xf2, 0x20, 0x10, 0x00, 0x01, 0x00, 0x00, 0x00, 0x65, // ...e.... ......e
|
||||||
|
0x00, 0x00, 0x03, 0x32, 0x20, 0x10, 0x00, 0x02, 0x00, 0x00, 0x00, 0x68, 0x00, 0x00, 0x02, 0x05, // ...2 ......h....
|
||||||
|
0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x05, 0x32, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46, // ...6...2.......F
|
||||||
|
0x10, 0x10, 0x00, 0x02, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x05, 0xc2, 0x00, 0x10, 0x00, 0x00, // .......6........
|
||||||
|
0x00, 0x00, 0x00, 0xa6, 0x1e, 0x10, 0x00, 0x02, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x05, 0x12, // ...........6....
|
||||||
|
0x00, 0x10, 0x00, 0x01, 0x00, 0x00, 0x00, 0x0a, 0x10, 0x10, 0x00, 0x03, 0x00, 0x00, 0x00, 0x36, // ...............6
|
||||||
|
0x00, 0x00, 0x05, 0x12, 0x00, 0x10, 0x00, 0x02, 0x00, 0x00, 0x00, 0x1a, 0x10, 0x10, 0x00, 0x03, // ................
|
||||||
|
0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x05, 0x22, 0x00, 0x10, 0x00, 0x02, 0x00, 0x00, 0x00, 0x2a, // ...6...".......*
|
||||||
|
0x10, 0x10, 0x00, 0x03, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x05, 0x12, 0x00, 0x10, 0x00, 0x03, // .......6........
|
||||||
|
0x00, 0x00, 0x00, 0x3a, 0x10, 0x10, 0x00, 0x03, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x05, 0x22, // ...:.......6..."
|
||||||
|
0x00, 0x10, 0x00, 0x03, 0x00, 0x00, 0x00, 0x0a, 0x10, 0x10, 0x00, 0x04, 0x00, 0x00, 0x00, 0x4d, // ...............M
|
||||||
|
0x00, 0x00, 0x06, 0x00, 0xd0, 0x00, 0x00, 0x22, 0x00, 0x10, 0x00, 0x01, 0x00, 0x00, 0x00, 0x0a, // ......."........
|
||||||
|
0x00, 0x10, 0x00, 0x01, 0x00, 0x00, 0x00, 0x4d, 0x00, 0x00, 0x06, 0x12, 0x00, 0x10, 0x00, 0x01, // .......M........
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0xd0, 0x00, 0x00, 0x0a, 0x00, 0x10, 0x00, 0x01, 0x00, 0x00, 0x00, 0x36, // ...............6
|
||||||
|
0x00, 0x00, 0x08, 0xc2, 0x00, 0x10, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x40, 0x00, 0x00, 0x00, // ............@...
|
||||||
|
0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0xbf, 0x00, 0x00, 0x00, 0xbf, 0x00, // ................
|
||||||
|
0x00, 0x00, 0x07, 0xc2, 0x00, 0x10, 0x00, 0x01, 0x00, 0x00, 0x00, 0xa6, 0x0e, 0x10, 0x00, 0x01, // ................
|
||||||
|
0x00, 0x00, 0x00, 0x06, 0x14, 0x10, 0x00, 0x01, 0x00, 0x00, 0x00, 0x38, 0x00, 0x00, 0x07, 0x42, // ...........8...B
|
||||||
|
0x00, 0x10, 0x00, 0x02, 0x00, 0x00, 0x00, 0x1a, 0x00, 0x10, 0x00, 0x01, 0x00, 0x00, 0x00, 0x2a, // ...............*
|
||||||
|
0x00, 0x10, 0x00, 0x01, 0x00, 0x00, 0x00, 0x38, 0x00, 0x00, 0x07, 0x82, 0x00, 0x10, 0x00, 0x02, // .......8........
|
||||||
|
0x00, 0x00, 0x00, 0x0a, 0x00, 0x10, 0x00, 0x01, 0x00, 0x00, 0x00, 0x3a, 0x00, 0x10, 0x00, 0x01, // ...........:....
|
||||||
|
0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x06, 0x82, 0x00, 0x10, 0x00, 0x02, 0x00, 0x00, 0x00, 0x3a, // ...6...........:
|
||||||
|
0x00, 0x10, 0x80, 0x41, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x12, // ...A............
|
||||||
|
0x00, 0x10, 0x00, 0x04, 0x00, 0x00, 0x00, 0x3a, 0x00, 0x10, 0x00, 0x02, 0x00, 0x00, 0x00, 0x2a, // .......:.......*
|
||||||
|
0x00, 0x10, 0x00, 0x02, 0x00, 0x00, 0x00, 0x38, 0x00, 0x00, 0x07, 0x12, 0x00, 0x10, 0x00, 0x01, // .......8........
|
||||||
|
0x00, 0x00, 0x00, 0x0a, 0x00, 0x10, 0x00, 0x01, 0x00, 0x00, 0x00, 0x2a, 0x00, 0x10, 0x00, 0x01, // ...........*....
|
||||||
|
0x00, 0x00, 0x00, 0x38, 0x00, 0x00, 0x07, 0x22, 0x00, 0x10, 0x00, 0x01, 0x00, 0x00, 0x00, 0x1a, // ...8..."........
|
||||||
|
0x00, 0x10, 0x00, 0x01, 0x00, 0x00, 0x00, 0x3a, 0x00, 0x10, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, // .......:........
|
||||||
|
0x00, 0x00, 0x07, 0x22, 0x00, 0x10, 0x00, 0x04, 0x00, 0x00, 0x00, 0x1a, 0x00, 0x10, 0x00, 0x01, // ..."............
|
||||||
|
0x00, 0x00, 0x00, 0x0a, 0x00, 0x10, 0x00, 0x01, 0x00, 0x00, 0x00, 0x38, 0x00, 0x00, 0x07, 0xc2, // ...........8....
|
||||||
|
0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0xa6, 0x0e, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, // ................
|
||||||
|
0x04, 0x10, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x32, 0x00, 0x10, 0x00, 0x00, // ...........2....
|
||||||
|
0x00, 0x00, 0x00, 0x46, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0xe6, 0x0a, 0x10, 0x00, 0x00, // ...F............
|
||||||
|
0x00, 0x00, 0x00, 0x38, 0x00, 0x00, 0x08, 0xf2, 0x00, 0x10, 0x00, 0x01, 0x00, 0x00, 0x00, 0x06, // ...8............
|
||||||
|
0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46, 0x8e, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // .......F. ......
|
||||||
|
0x00, 0x00, 0x00, 0x38, 0x00, 0x00, 0x08, 0xf2, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x56, // ...8...........V
|
||||||
|
0x05, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46, 0x8e, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, // .......F. ......
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xf2, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46, // ...............F
|
||||||
|
0x0e, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46, 0x0e, 0x10, 0x00, 0x01, 0x00, 0x00, 0x00, 0x38, // .......F.......8
|
||||||
|
0x00, 0x00, 0x0b, 0xf2, 0x00, 0x10, 0x00, 0x01, 0x00, 0x00, 0x00, 0x46, 0x8e, 0x20, 0x00, 0x00, // ...........F. ..
|
||||||
|
0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x02, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // ........@.......
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xf2, // ................
|
||||||
|
0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46, 0x0e, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46, // .......F.......F
|
||||||
|
0x0e, 0x10, 0x00, 0x01, 0x00, 0x00, 0x00, 0x38, 0x00, 0x00, 0x0b, 0xf2, 0x00, 0x10, 0x00, 0x01, // .......8........
|
||||||
|
0x00, 0x00, 0x00, 0x46, 0x8e, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x02, // ...F. ..........
|
||||||
|
0x40, 0x00, 0x00, 0x00, 0x00, 0x80, 0x3f, 0x00, 0x00, 0x80, 0x3f, 0x00, 0x00, 0x80, 0x3f, 0x00, // @.....?...?...?.
|
||||||
|
0x00, 0x80, 0x3f, 0x00, 0x00, 0x00, 0x07, 0xf2, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46, // ..?............F
|
||||||
|
0x0e, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46, 0x0e, 0x10, 0x00, 0x01, 0x00, 0x00, 0x00, 0x36, // .......F.......6
|
||||||
|
0x00, 0x00, 0x05, 0x12, 0x00, 0x10, 0x00, 0x02, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x10, 0x00, 0x02, // ................
|
||||||
|
0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x05, 0x22, 0x00, 0x10, 0x00, 0x02, 0x00, 0x00, 0x00, 0x1a, // ...6..."........
|
||||||
|
0x00, 0x10, 0x00, 0x02, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x05, 0x12, 0x00, 0x10, 0x00, 0x03, // .......6........
|
||||||
|
0x00, 0x00, 0x00, 0x0a, 0x00, 0x10, 0x00, 0x03, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x05, 0x22, // ...........6..."
|
||||||
|
0x00, 0x10, 0x00, 0x03, 0x00, 0x00, 0x00, 0x1a, 0x00, 0x10, 0x00, 0x03, 0x00, 0x00, 0x00, 0x36, // ...............6
|
||||||
|
0x00, 0x00, 0x05, 0x32, 0x00, 0x10, 0x00, 0x01, 0x00, 0x00, 0x00, 0x46, 0x10, 0x10, 0x00, 0x01, // ...2.......F....
|
||||||
|
0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x06, 0xc2, 0x00, 0x10, 0x00, 0x01, 0x00, 0x00, 0x00, 0x06, // ...6............
|
||||||
|
0x04, 0x10, 0x80, 0x41, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xc2, // ...A............
|
||||||
|
0x00, 0x10, 0x00, 0x01, 0x00, 0x00, 0x00, 0xa6, 0x0e, 0x10, 0x00, 0x01, 0x00, 0x00, 0x00, 0x06, // ................
|
||||||
|
0x04, 0x10, 0x00, 0x03, 0x00, 0x00, 0x00, 0x38, 0x00, 0x00, 0x07, 0x32, 0x00, 0x10, 0x00, 0x01, // .......8...2....
|
||||||
|
0x00, 0x00, 0x00, 0xe6, 0x0a, 0x10, 0x00, 0x01, 0x00, 0x00, 0x00, 0x46, 0x00, 0x10, 0x00, 0x01, // ...........F....
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x32, 0x00, 0x10, 0x00, 0x01, 0x00, 0x00, 0x00, 0x46, // .......2.......F
|
||||||
|
0x00, 0x10, 0x00, 0x01, 0x00, 0x00, 0x00, 0x46, 0x00, 0x10, 0x00, 0x02, 0x00, 0x00, 0x00, 0x36, // .......F.......6
|
||||||
|
0x00, 0x00, 0x05, 0x32, 0x00, 0x10, 0x00, 0x01, 0x00, 0x00, 0x00, 0x46, 0x00, 0x10, 0x00, 0x01, // ...2.......F....
|
||||||
|
0x00, 0x00, 0x00, 0x38, 0x00, 0x00, 0x07, 0xf2, 0x00, 0x10, 0x00, 0x02, 0x00, 0x00, 0x00, 0x46, // ...8...........F
|
||||||
|
0x1e, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46, 0x1e, 0x10, 0x00, 0x06, 0x00, 0x00, 0x00, 0x36, // .......F.......6
|
||||||
|
0x00, 0x00, 0x05, 0xf2, 0x20, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46, 0x0e, 0x10, 0x00, 0x00, // .... ......F....
|
||||||
|
0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x05, 0xf2, 0x20, 0x10, 0x00, 0x01, 0x00, 0x00, 0x00, 0x46, // ...6.... ......F
|
||||||
|
0x0e, 0x10, 0x00, 0x02, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x05, 0x32, 0x20, 0x10, 0x00, 0x02, // .......6...2 ...
|
||||||
|
0x00, 0x00, 0x00, 0x46, 0x00, 0x10, 0x00, 0x01, 0x00, 0x00, 0x00, 0x3e, 0x00, 0x00, 0x01, 0x00, // ...F.......>....
|
||||||
|
0x07, 0x05, 0x00, 0x01, 0x00, 0x17, 0x00, 0x16, 0x00, 0x15, 0x00, 0x14, 0x00, 0x13, 0x00, 0x40, // ...............@
|
||||||
|
0x00, // .
|
||||||
|
};
|
||||||
|
static const unsigned int vs_sprite_dx11_len = sizeof(vs_sprite_dx11);
|
||||||
|
|||||||
@ -1,6 +1,28 @@
|
|||||||
# InputModule - Input capture and conversion module
|
# InputModule - Input capture and conversion module
|
||||||
# Converts native input events (SDL, GLFW, etc.) to IIO messages
|
# Converts native input events (SDL, GLFW, etc.) to IIO messages
|
||||||
|
|
||||||
|
# Find SDL2 - REQUIRED for InputModule to function
|
||||||
|
find_package(SDL2 QUIET)
|
||||||
|
|
||||||
|
# Check if SDL2 is available
|
||||||
|
set(SDL2_USABLE FALSE)
|
||||||
|
if(SDL2_FOUND)
|
||||||
|
set(SDL2_USABLE TRUE)
|
||||||
|
message(STATUS "InputModule: SDL2 found via find_package")
|
||||||
|
elseif(UNIX AND EXISTS "/usr/include/SDL2/SDL.h")
|
||||||
|
set(SDL2_USABLE TRUE)
|
||||||
|
message(STATUS "InputModule: SDL2 found at system path (Linux)")
|
||||||
|
endif()
|
||||||
|
|
||||||
|
if(NOT SDL2_USABLE)
|
||||||
|
message(WARNING "InputModule: SDL2 not found - InputModule will NOT be built")
|
||||||
|
message(STATUS " On Windows MinGW, install via: pacman -S mingw-w64-ucrt-x86_64-SDL2")
|
||||||
|
message(STATUS " On Ubuntu/Debian, install via: apt install libsdl2-dev")
|
||||||
|
# Set a variable so parent can know InputModule was skipped
|
||||||
|
set(GROVE_INPUT_MODULE_SKIPPED TRUE PARENT_SCOPE)
|
||||||
|
return()
|
||||||
|
endif()
|
||||||
|
|
||||||
add_library(InputModule SHARED
|
add_library(InputModule SHARED
|
||||||
InputModule.cpp
|
InputModule.cpp
|
||||||
Core/InputState.cpp
|
Core/InputState.cpp
|
||||||
@ -8,17 +30,13 @@ add_library(InputModule SHARED
|
|||||||
Backends/SDLBackend.cpp
|
Backends/SDLBackend.cpp
|
||||||
)
|
)
|
||||||
|
|
||||||
target_include_directories(InputModule
|
if(SDL2_FOUND)
|
||||||
|
target_include_directories(InputModule
|
||||||
PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}
|
PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}
|
||||||
PRIVATE
|
PRIVATE
|
||||||
${CMAKE_SOURCE_DIR}/include
|
${CMAKE_SOURCE_DIR}/include
|
||||||
/usr/include/SDL2
|
)
|
||||||
)
|
|
||||||
|
|
||||||
# Try to find SDL2, but don't fail if not found (use system paths)
|
|
||||||
find_package(SDL2 QUIET)
|
|
||||||
|
|
||||||
if(SDL2_FOUND)
|
|
||||||
target_link_libraries(InputModule
|
target_link_libraries(InputModule
|
||||||
PRIVATE
|
PRIVATE
|
||||||
GroveEngine::impl
|
GroveEngine::impl
|
||||||
@ -27,7 +45,14 @@ if(SDL2_FOUND)
|
|||||||
spdlog::spdlog
|
spdlog::spdlog
|
||||||
)
|
)
|
||||||
else()
|
else()
|
||||||
# Fallback to system SDL2
|
# Linux system SDL2
|
||||||
|
target_include_directories(InputModule
|
||||||
|
PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}
|
||||||
|
PRIVATE
|
||||||
|
${CMAKE_SOURCE_DIR}/include
|
||||||
|
/usr/include/SDL2
|
||||||
|
)
|
||||||
|
|
||||||
target_link_libraries(InputModule
|
target_link_libraries(InputModule
|
||||||
PRIVATE
|
PRIVATE
|
||||||
GroveEngine::impl
|
GroveEngine::impl
|
||||||
|
|||||||
@ -70,3 +70,56 @@ if(UNIX AND NOT APPLE)
|
|||||||
dl
|
dl
|
||||||
)
|
)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
# ============================================================================
|
||||||
|
# UIModule Static Library (for tests that can't use DLL loading)
|
||||||
|
# ============================================================================
|
||||||
|
|
||||||
|
add_library(UIModule_static STATIC
|
||||||
|
# Main module
|
||||||
|
UIModule.cpp
|
||||||
|
|
||||||
|
# Core
|
||||||
|
Core/UITree.cpp
|
||||||
|
Core/UILayout.cpp
|
||||||
|
Core/UIContext.cpp
|
||||||
|
Core/UIStyle.cpp
|
||||||
|
Core/UITooltip.cpp
|
||||||
|
|
||||||
|
# Widgets
|
||||||
|
Widgets/UIPanel.cpp
|
||||||
|
Widgets/UILabel.cpp
|
||||||
|
Widgets/UIButton.cpp
|
||||||
|
Widgets/UIImage.cpp
|
||||||
|
Widgets/UISlider.cpp
|
||||||
|
Widgets/UICheckbox.cpp
|
||||||
|
Widgets/UIProgressBar.cpp
|
||||||
|
Widgets/UITextInput.cpp
|
||||||
|
Widgets/UIScrollPanel.cpp
|
||||||
|
|
||||||
|
# Rendering
|
||||||
|
Rendering/UIRenderer.cpp
|
||||||
|
)
|
||||||
|
|
||||||
|
target_include_directories(UIModule_static PUBLIC
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/../../include
|
||||||
|
)
|
||||||
|
|
||||||
|
target_link_libraries(UIModule_static PUBLIC
|
||||||
|
GroveEngine::impl
|
||||||
|
spdlog::spdlog
|
||||||
|
nlohmann_json::nlohmann_json
|
||||||
|
)
|
||||||
|
|
||||||
|
target_compile_features(UIModule_static PRIVATE cxx_std_17)
|
||||||
|
|
||||||
|
# Mark as static build to skip C exports (avoids multiple definition errors)
|
||||||
|
target_compile_definitions(UIModule_static PRIVATE GROVE_MODULE_STATIC)
|
||||||
|
|
||||||
|
if(WIN32)
|
||||||
|
target_compile_definitions(UIModule_static PRIVATE
|
||||||
|
WIN32_LEAN_AND_MEAN
|
||||||
|
NOMINMAX
|
||||||
|
)
|
||||||
|
endif()
|
||||||
|
|||||||
@ -48,7 +48,8 @@ public:
|
|||||||
keyCode = 0;
|
keyCode = 0;
|
||||||
keyChar = 0;
|
keyChar = 0;
|
||||||
mouseWheelDelta = 0.0f;
|
mouseWheelDelta = 0.0f;
|
||||||
hoveredWidgetId.clear();
|
// Note: hoveredWidgetId is NOT cleared here - it persists
|
||||||
|
// and is updated by hit testing during updateUI()
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@ -66,61 +66,60 @@ void UITree::registerDefaultWidgets() {
|
|||||||
button->onClick = node.getString("onClick", "");
|
button->onClick = node.getString("onClick", "");
|
||||||
button->enabled = node.getBool("enabled", true);
|
button->enabled = node.getBool("enabled", true);
|
||||||
|
|
||||||
|
// Helper lambda to parse a button style
|
||||||
|
auto parseButtonStyle = [](IDataNode* styleNode, ButtonStyle& style) {
|
||||||
|
if (!styleNode) return;
|
||||||
|
|
||||||
|
std::string bgColorStr = styleNode->getString("bgColor", "");
|
||||||
|
if (bgColorStr.size() >= 2 && (bgColorStr.substr(0, 2) == "0x" || bgColorStr.substr(0, 2) == "0X")) {
|
||||||
|
style.bgColor = static_cast<uint32_t>(std::stoul(bgColorStr, nullptr, 16));
|
||||||
|
}
|
||||||
|
std::string textColorStr = styleNode->getString("textColor", "");
|
||||||
|
if (textColorStr.size() >= 2 && (textColorStr.substr(0, 2) == "0x" || textColorStr.substr(0, 2) == "0X")) {
|
||||||
|
style.textColor = static_cast<uint32_t>(std::stoul(textColorStr, nullptr, 16));
|
||||||
|
}
|
||||||
|
std::string borderColorStr = styleNode->getString("borderColor", "");
|
||||||
|
if (borderColorStr.size() >= 2 && (borderColorStr.substr(0, 2) == "0x" || borderColorStr.substr(0, 2) == "0X")) {
|
||||||
|
style.borderColor = static_cast<uint32_t>(std::stoul(borderColorStr, nullptr, 16));
|
||||||
|
}
|
||||||
|
style.borderWidth = static_cast<float>(styleNode->getDouble("borderWidth", style.borderWidth));
|
||||||
|
style.borderRadius = static_cast<float>(styleNode->getDouble("borderRadius", style.borderRadius));
|
||||||
|
style.textureId = styleNode->getInt("textureId", 0);
|
||||||
|
style.useTexture = style.textureId > 0;
|
||||||
|
};
|
||||||
|
|
||||||
// Parse style (const_cast safe for read-only operations)
|
// Parse style (const_cast safe for read-only operations)
|
||||||
auto& mutableNode = const_cast<IDataNode&>(node);
|
auto& mutableNode = const_cast<IDataNode&>(node);
|
||||||
if (auto* style = mutableNode.getChildReadOnly("style")) {
|
if (auto* style = mutableNode.getChildReadOnly("style")) {
|
||||||
// Normal style
|
// Normal style
|
||||||
if (auto* normalStyle = style->getChildReadOnly("normal")) {
|
if (auto* normalStyle = style->getChildReadOnly("normal")) {
|
||||||
std::string bgColorStr = normalStyle->getString("bgColor", "0x444444FF");
|
parseButtonStyle(normalStyle, button->normalStyle);
|
||||||
if (bgColorStr.size() >= 2 && (bgColorStr.substr(0, 2) == "0x" || bgColorStr.substr(0, 2) == "0X")) {
|
|
||||||
button->normalStyle.bgColor = static_cast<uint32_t>(std::stoul(bgColorStr, nullptr, 16));
|
|
||||||
}
|
|
||||||
std::string textColorStr = normalStyle->getString("textColor", "0xFFFFFFFF");
|
|
||||||
if (textColorStr.size() >= 2 && (textColorStr.substr(0, 2) == "0x" || textColorStr.substr(0, 2) == "0X")) {
|
|
||||||
button->normalStyle.textColor = static_cast<uint32_t>(std::stoul(textColorStr, nullptr, 16));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Hover style
|
// Hover style
|
||||||
if (auto* hoverStyle = style->getChildReadOnly("hover")) {
|
if (auto* hoverStyle = style->getChildReadOnly("hover")) {
|
||||||
std::string bgColorStr = hoverStyle->getString("bgColor", "0x666666FF");
|
parseButtonStyle(hoverStyle, button->hoverStyle);
|
||||||
if (bgColorStr.size() >= 2 && (bgColorStr.substr(0, 2) == "0x" || bgColorStr.substr(0, 2) == "0X")) {
|
button->hoverStyleSet = true;
|
||||||
button->hoverStyle.bgColor = static_cast<uint32_t>(std::stoul(bgColorStr, nullptr, 16));
|
|
||||||
}
|
|
||||||
std::string textColorStr = hoverStyle->getString("textColor", "0xFFFFFFFF");
|
|
||||||
if (textColorStr.size() >= 2 && (textColorStr.substr(0, 2) == "0x" || textColorStr.substr(0, 2) == "0X")) {
|
|
||||||
button->hoverStyle.textColor = static_cast<uint32_t>(std::stoul(textColorStr, nullptr, 16));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Pressed style
|
// Pressed style
|
||||||
if (auto* pressedStyle = style->getChildReadOnly("pressed")) {
|
if (auto* pressedStyle = style->getChildReadOnly("pressed")) {
|
||||||
std::string bgColorStr = pressedStyle->getString("bgColor", "0x333333FF");
|
parseButtonStyle(pressedStyle, button->pressedStyle);
|
||||||
if (bgColorStr.size() >= 2 && (bgColorStr.substr(0, 2) == "0x" || bgColorStr.substr(0, 2) == "0X")) {
|
button->pressedStyleSet = true;
|
||||||
button->pressedStyle.bgColor = static_cast<uint32_t>(std::stoul(bgColorStr, nullptr, 16));
|
|
||||||
}
|
|
||||||
std::string textColorStr = pressedStyle->getString("textColor", "0xFFFFFFFF");
|
|
||||||
if (textColorStr.size() >= 2 && (textColorStr.substr(0, 2) == "0x" || textColorStr.substr(0, 2) == "0X")) {
|
|
||||||
button->pressedStyle.textColor = static_cast<uint32_t>(std::stoul(textColorStr, nullptr, 16));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Disabled style
|
// Disabled style
|
||||||
if (auto* disabledStyle = style->getChildReadOnly("disabled")) {
|
if (auto* disabledStyle = style->getChildReadOnly("disabled")) {
|
||||||
std::string bgColorStr = disabledStyle->getString("bgColor", "0x222222FF");
|
parseButtonStyle(disabledStyle, button->disabledStyle);
|
||||||
if (bgColorStr.size() >= 2 && (bgColorStr.substr(0, 2) == "0x" || bgColorStr.substr(0, 2) == "0X")) {
|
|
||||||
button->disabledStyle.bgColor = static_cast<uint32_t>(std::stoul(bgColorStr, nullptr, 16));
|
|
||||||
}
|
|
||||||
std::string textColorStr = disabledStyle->getString("textColor", "0x666666FF");
|
|
||||||
if (textColorStr.size() >= 2 && (textColorStr.substr(0, 2) == "0x" || textColorStr.substr(0, 2) == "0X")) {
|
|
||||||
button->disabledStyle.textColor = static_cast<uint32_t>(std::stoul(textColorStr, nullptr, 16));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Font size from style root
|
// Font size from style root
|
||||||
button->fontSize = static_cast<float>(style->getDouble("fontSize", 16.0));
|
button->fontSize = static_cast<float>(style->getDouble("fontSize", 16.0));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Auto-generate hover/pressed styles if not explicitly set
|
||||||
|
button->generateDefaultStyles();
|
||||||
|
|
||||||
return button;
|
return button;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
#include "UIRenderer.h"
|
#include "UIRenderer.h"
|
||||||
#include <grove/JsonDataNode.h>
|
#include <grove/JsonDataNode.h>
|
||||||
|
#include <spdlog/spdlog.h>
|
||||||
|
|
||||||
namespace grove {
|
namespace grove {
|
||||||
|
|
||||||
@ -10,11 +11,19 @@ UIRenderer::UIRenderer(IIO* io)
|
|||||||
void UIRenderer::drawRect(float x, float y, float w, float h, uint32_t color) {
|
void UIRenderer::drawRect(float x, float y, float w, float h, uint32_t color) {
|
||||||
if (!m_io) return;
|
if (!m_io) return;
|
||||||
|
|
||||||
|
// DEBUG: Log color being sent
|
||||||
|
static uint32_t lastLoggedColor = 0;
|
||||||
|
if (color != lastLoggedColor && (color == 0xFF0000FF || color == 0x00FF00FF)) {
|
||||||
|
spdlog::info("UIRenderer::drawRect color=0x{:08X} at ({}, {})", color, x, y);
|
||||||
|
lastLoggedColor = color;
|
||||||
|
}
|
||||||
|
|
||||||
auto sprite = std::make_unique<JsonDataNode>("sprite");
|
auto sprite = std::make_unique<JsonDataNode>("sprite");
|
||||||
sprite->setDouble("x", static_cast<double>(x));
|
// Position at center of rect (sprite shader centers quads)
|
||||||
sprite->setDouble("y", static_cast<double>(y));
|
sprite->setDouble("x", static_cast<double>(x + w * 0.5f));
|
||||||
sprite->setDouble("width", static_cast<double>(w));
|
sprite->setDouble("y", static_cast<double>(y + h * 0.5f));
|
||||||
sprite->setDouble("height", static_cast<double>(h));
|
sprite->setDouble("scaleX", static_cast<double>(w));
|
||||||
|
sprite->setDouble("scaleY", static_cast<double>(h));
|
||||||
sprite->setInt("color", static_cast<int>(color));
|
sprite->setInt("color", static_cast<int>(color));
|
||||||
sprite->setInt("textureId", 0); // White/solid color texture
|
sprite->setInt("textureId", 0); // White/solid color texture
|
||||||
sprite->setInt("layer", nextLayer());
|
sprite->setInt("layer", nextLayer());
|
||||||
@ -40,10 +49,11 @@ void UIRenderer::drawSprite(float x, float y, float w, float h, int textureId, u
|
|||||||
if (!m_io) return;
|
if (!m_io) return;
|
||||||
|
|
||||||
auto sprite = std::make_unique<JsonDataNode>("sprite");
|
auto sprite = std::make_unique<JsonDataNode>("sprite");
|
||||||
sprite->setDouble("x", static_cast<double>(x));
|
// Position at center of sprite (sprite shader centers quads)
|
||||||
sprite->setDouble("y", static_cast<double>(y));
|
sprite->setDouble("x", static_cast<double>(x + w * 0.5f));
|
||||||
sprite->setDouble("width", static_cast<double>(w));
|
sprite->setDouble("y", static_cast<double>(y + h * 0.5f));
|
||||||
sprite->setDouble("height", static_cast<double>(h));
|
sprite->setDouble("scaleX", static_cast<double>(w));
|
||||||
|
sprite->setDouble("scaleY", static_cast<double>(h));
|
||||||
sprite->setInt("color", static_cast<int>(color));
|
sprite->setInt("color", static_cast<int>(color));
|
||||||
sprite->setInt("textureId", textureId);
|
sprite->setInt("textureId", textureId);
|
||||||
sprite->setInt("layer", nextLayer());
|
sprite->setInt("layer", nextLayer());
|
||||||
|
|||||||
@ -424,8 +424,11 @@ std::unique_ptr<IDataNode> UIModule::getHealthStatus() {
|
|||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
// C Export (required for dlopen/LoadLibrary)
|
// C Export (required for dlopen/LoadLibrary)
|
||||||
|
// Skip when building as static library to avoid multiple definition errors
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
|
|
||||||
|
#ifndef GROVE_MODULE_STATIC
|
||||||
|
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
#define GROVE_MODULE_EXPORT __declspec(dllexport)
|
#define GROVE_MODULE_EXPORT __declspec(dllexport)
|
||||||
#else
|
#else
|
||||||
@ -443,3 +446,5 @@ GROVE_MODULE_EXPORT void destroyModule(grove::IModule* module) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#endif // GROVE_MODULE_STATIC
|
||||||
|
|||||||
@ -2,6 +2,8 @@
|
|||||||
#include "../Core/UIContext.h"
|
#include "../Core/UIContext.h"
|
||||||
#include "../Rendering/UIRenderer.h"
|
#include "../Rendering/UIRenderer.h"
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
#include <cmath>
|
||||||
|
#include <spdlog/spdlog.h>
|
||||||
|
|
||||||
namespace grove {
|
namespace grove {
|
||||||
|
|
||||||
@ -30,8 +32,12 @@ void UIButton::update(UIContext& ctx, float deltaTime) {
|
|||||||
void UIButton::render(UIRenderer& renderer) {
|
void UIButton::render(UIRenderer& renderer) {
|
||||||
const ButtonStyle& style = getCurrentStyle();
|
const ButtonStyle& style = getCurrentStyle();
|
||||||
|
|
||||||
// Render background rectangle
|
// Render background (texture or solid color)
|
||||||
|
if (style.useTexture && style.textureId > 0) {
|
||||||
|
renderer.drawSprite(absX, absY, width, height, style.textureId, style.bgColor);
|
||||||
|
} else {
|
||||||
renderer.drawRect(absX, absY, width, height, style.bgColor);
|
renderer.drawRect(absX, absY, width, height, style.bgColor);
|
||||||
|
}
|
||||||
|
|
||||||
// Render border if specified
|
// Render border if specified
|
||||||
if (style.borderWidth > 0.0f) {
|
if (style.borderWidth > 0.0f) {
|
||||||
@ -53,6 +59,39 @@ void UIButton::render(UIRenderer& renderer) {
|
|||||||
renderChildren(renderer);
|
renderChildren(renderer);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void UIButton::generateDefaultStyles() {
|
||||||
|
// If hover style wasn't explicitly set, lighten normal color
|
||||||
|
if (!hoverStyleSet) {
|
||||||
|
hoverStyle = normalStyle;
|
||||||
|
hoverStyle.bgColor = adjustBrightness(normalStyle.bgColor, 1.2f);
|
||||||
|
}
|
||||||
|
|
||||||
|
// If pressed style wasn't explicitly set, darken normal color
|
||||||
|
if (!pressedStyleSet) {
|
||||||
|
pressedStyle = normalStyle;
|
||||||
|
pressedStyle.bgColor = adjustBrightness(normalStyle.bgColor, 0.7f);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Disabled style: desaturate and dim
|
||||||
|
disabledStyle = normalStyle;
|
||||||
|
disabledStyle.bgColor = adjustBrightness(normalStyle.bgColor, 0.5f);
|
||||||
|
disabledStyle.textColor = 0x888888FF;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t UIButton::adjustBrightness(uint32_t color, float factor) {
|
||||||
|
uint8_t r = (color >> 24) & 0xFF;
|
||||||
|
uint8_t g = (color >> 16) & 0xFF;
|
||||||
|
uint8_t b = (color >> 8) & 0xFF;
|
||||||
|
uint8_t a = color & 0xFF;
|
||||||
|
|
||||||
|
// Adjust RGB, clamp to 0-255
|
||||||
|
r = static_cast<uint8_t>(std::min(255.0f, std::max(0.0f, r * factor)));
|
||||||
|
g = static_cast<uint8_t>(std::min(255.0f, std::max(0.0f, g * factor)));
|
||||||
|
b = static_cast<uint8_t>(std::min(255.0f, std::max(0.0f, b * factor)));
|
||||||
|
|
||||||
|
return (r << 24) | (g << 16) | (b << 8) | a;
|
||||||
|
}
|
||||||
|
|
||||||
bool UIButton::containsPoint(float px, float py) const {
|
bool UIButton::containsPoint(float px, float py) const {
|
||||||
return px >= absX && px < absX + width &&
|
return px >= absX && px < absX + width &&
|
||||||
py >= absY && py < absY + height;
|
py >= absY && py < absY + height;
|
||||||
|
|||||||
@ -25,6 +25,8 @@ struct ButtonStyle {
|
|||||||
uint32_t borderColor = 0x000000FF;
|
uint32_t borderColor = 0x000000FF;
|
||||||
float borderWidth = 0.0f;
|
float borderWidth = 0.0f;
|
||||||
float borderRadius = 0.0f;
|
float borderRadius = 0.0f;
|
||||||
|
int textureId = 0; // 0 = no texture (solid color), >0 = texture ID
|
||||||
|
bool useTexture = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -71,16 +73,34 @@ public:
|
|||||||
ButtonStyle pressedStyle;
|
ButtonStyle pressedStyle;
|
||||||
ButtonStyle disabledStyle;
|
ButtonStyle disabledStyle;
|
||||||
|
|
||||||
|
// Track if styles were explicitly set (for auto-generation)
|
||||||
|
bool hoverStyleSet = false;
|
||||||
|
bool pressedStyleSet = false;
|
||||||
|
|
||||||
// Current state
|
// Current state
|
||||||
ButtonState state = ButtonState::Normal;
|
ButtonState state = ButtonState::Normal;
|
||||||
bool isHovered = false;
|
bool isHovered = false;
|
||||||
bool isPressed = false;
|
bool isPressed = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Auto-generate hover/pressed styles from normal style
|
||||||
|
* Call this after setting normalStyle if hover/pressed weren't explicitly set
|
||||||
|
*/
|
||||||
|
void generateDefaultStyles();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
/**
|
/**
|
||||||
* @brief Get the appropriate style for current state
|
* @brief Get the appropriate style for current state
|
||||||
*/
|
*/
|
||||||
const ButtonStyle& getCurrentStyle() const;
|
const ButtonStyle& getCurrentStyle() const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Adjust color brightness
|
||||||
|
* @param color RGBA color
|
||||||
|
* @param factor >1 to lighten, <1 to darken
|
||||||
|
* @return Adjusted color
|
||||||
|
*/
|
||||||
|
static uint32_t adjustBrightness(uint32_t color, float factor);
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace grove
|
} // namespace grove
|
||||||
|
|||||||
@ -2,6 +2,7 @@
|
|||||||
#include "../Core/UIContext.h"
|
#include "../Core/UIContext.h"
|
||||||
#include "../Core/UILayout.h"
|
#include "../Core/UILayout.h"
|
||||||
#include "../Rendering/UIRenderer.h"
|
#include "../Rendering/UIRenderer.h"
|
||||||
|
#include <spdlog/spdlog.h>
|
||||||
|
|
||||||
namespace grove {
|
namespace grove {
|
||||||
|
|
||||||
|
|||||||
@ -425,7 +425,7 @@ void DebugEngine::processClientMessages() {
|
|||||||
logger->trace("📨 Client {} has {} pending message(s)", i, messageCount);
|
logger->trace("📨 Client {} has {} pending message(s)", i, messageCount);
|
||||||
|
|
||||||
// Process a few messages per frame to avoid blocking
|
// Process a few messages per frame to avoid blocking
|
||||||
int messagesToProcess = std::min(messageCount, 5);
|
int messagesToProcess = (std::min)(messageCount, 5);
|
||||||
|
|
||||||
for (int j = 0; j < messagesToProcess; ++j) {
|
for (int j = 0; j < messagesToProcess; ++j) {
|
||||||
try {
|
try {
|
||||||
@ -452,7 +452,7 @@ void DebugEngine::processCoordinatorMessages() {
|
|||||||
logger->trace("📨 Coordinator has {} pending message(s)", messageCount);
|
logger->trace("📨 Coordinator has {} pending message(s)", messageCount);
|
||||||
|
|
||||||
// Process coordinator messages with higher priority
|
// Process coordinator messages with higher priority
|
||||||
int messagesToProcess = std::min(messageCount, 10);
|
int messagesToProcess = (std::min)(messageCount, 10);
|
||||||
|
|
||||||
for (int i = 0; i < messagesToProcess; ++i) {
|
for (int i = 0; i < messagesToProcess; ++i) {
|
||||||
try {
|
try {
|
||||||
|
|||||||
@ -14,8 +14,8 @@ IntraIOManager::IntraIOManager() {
|
|||||||
logger->info("🌐🔗 IntraIOManager created - Central message router initialized");
|
logger->info("🌐🔗 IntraIOManager created - Central message router initialized");
|
||||||
|
|
||||||
// TEMPORARY: Disable batch thread to debug Windows crash
|
// TEMPORARY: Disable batch thread to debug Windows crash
|
||||||
batchThreadRunning = true;
|
batchThreadRunning = false;
|
||||||
batchThread = std::thread(&IntraIOManager::batchFlushLoop, this);
|
// batchThread = std::thread(&IntraIOManager::batchFlushLoop, this);
|
||||||
logger->info("⚠️ Batch flush thread DISABLED (debugging Windows crash)");
|
logger->info("⚠️ Batch flush thread DISABLED (debugging Windows crash)");
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -26,6 +26,21 @@ IntraIOManager::~IntraIOManager() {
|
|||||||
if (batchThread.joinable()) {
|
if (batchThread.joinable()) {
|
||||||
batchThread.join();
|
batchThread.join();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// IMPORTANT: During static destruction order on Windows (especially MinGW GCC 15),
|
||||||
|
// spdlog's registry may be destroyed BEFORE this singleton destructor runs.
|
||||||
|
// Using a destroyed logger causes STATUS_STACK_BUFFER_OVERRUN (0xc0000409).
|
||||||
|
// We must check if our logger is still valid before using it.
|
||||||
|
bool loggerValid = false;
|
||||||
|
try {
|
||||||
|
// Check if spdlog registry still exists and our logger is registered
|
||||||
|
loggerValid = logger && spdlog::get(logger->name()) != nullptr;
|
||||||
|
} catch (...) {
|
||||||
|
// spdlog registry may throw during destruction
|
||||||
|
loggerValid = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (loggerValid) {
|
||||||
logger->info("🛑 Batch flush thread stopped");
|
logger->info("🛑 Batch flush thread stopped");
|
||||||
|
|
||||||
// Get stats before locking to avoid recursive lock
|
// Get stats before locking to avoid recursive lock
|
||||||
@ -34,6 +49,7 @@ IntraIOManager::~IntraIOManager() {
|
|||||||
logger->info(" Total routed messages: {}", stats["total_routed_messages"].get<size_t>());
|
logger->info(" Total routed messages: {}", stats["total_routed_messages"].get<size_t>());
|
||||||
logger->info(" Total routes: {}", stats["total_routes"].get<size_t>());
|
logger->info(" Total routes: {}", stats["total_routes"].get<size_t>());
|
||||||
logger->info(" Active instances: {}", stats["active_instances"].get<size_t>());
|
logger->info(" Active instances: {}", stats["active_instances"].get<size_t>());
|
||||||
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
std::unique_lock lock(managerMutex); // WRITE - exclusive access needed
|
std::unique_lock lock(managerMutex); // WRITE - exclusive access needed
|
||||||
@ -48,7 +64,9 @@ IntraIOManager::~IntraIOManager() {
|
|||||||
batchBuffers.clear();
|
batchBuffers.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (loggerValid) {
|
||||||
logger->info("🌐🔗 IntraIOManager destroyed");
|
logger->info("🌐🔗 IntraIOManager destroyed");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
std::shared_ptr<IntraIO> IntraIOManager::createInstance(const std::string& instanceId) {
|
std::shared_ptr<IntraIO> IntraIOManager::createInstance(const std::string& instanceId) {
|
||||||
|
|||||||
@ -35,29 +35,92 @@ std::unique_ptr<IDataNode> JsonDataNode::getChild(const std::string& name) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
IDataNode* JsonDataNode::getChildReadOnly(const std::string& name) {
|
IDataNode* JsonDataNode::getChildReadOnly(const std::string& name) {
|
||||||
|
// First check if we already have this child node created
|
||||||
auto it = m_children.find(name);
|
auto it = m_children.find(name);
|
||||||
if (it == m_children.end()) {
|
if (it != m_children.end()) {
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
// Return raw pointer without copying - valid as long as parent exists
|
|
||||||
return it->second.get();
|
return it->second.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
// If not found in m_children, check if m_data has this key as an object/array
|
||||||
|
// and create a child node on demand
|
||||||
|
if (m_data.is_object() && m_data.contains(name)) {
|
||||||
|
const auto& childData = m_data[name];
|
||||||
|
if (childData.is_object() || childData.is_array()) {
|
||||||
|
// Create a new child node from the JSON data
|
||||||
|
auto newChild = std::make_unique<JsonDataNode>(name, childData, this, m_readOnly);
|
||||||
|
m_children[name] = std::move(newChild);
|
||||||
|
return m_children[name].get();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle array access by numeric index (e.g., "0", "1", "2")
|
||||||
|
if (m_data.is_array()) {
|
||||||
|
try {
|
||||||
|
size_t index = std::stoul(name);
|
||||||
|
if (index < m_data.size()) {
|
||||||
|
const auto& childData = m_data[index];
|
||||||
|
if (childData.is_object() || childData.is_array()) {
|
||||||
|
auto newChild = std::make_unique<JsonDataNode>(name, childData, this, m_readOnly);
|
||||||
|
m_children[name] = std::move(newChild);
|
||||||
|
return m_children[name].get();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (...) {
|
||||||
|
// Not a valid numeric index, ignore
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<std::string> JsonDataNode::getChildNames() {
|
std::vector<std::string> JsonDataNode::getChildNames() {
|
||||||
std::vector<std::string> names;
|
std::vector<std::string> names;
|
||||||
names.reserve(m_children.size());
|
|
||||||
|
// First, add names from m_children (already materialized nodes)
|
||||||
for (const auto& [name, _] : m_children) {
|
for (const auto& [name, _] : m_children) {
|
||||||
names.push_back(name);
|
names.push_back(name);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Also include object keys from m_data that haven't been materialized yet
|
||||||
|
if (m_data.is_object()) {
|
||||||
|
for (auto& [key, value] : m_data.items()) {
|
||||||
|
if (value.is_object() || value.is_array()) {
|
||||||
|
// Only add if not already in the list
|
||||||
|
if (m_children.find(key) == m_children.end()) {
|
||||||
|
names.push_back(key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return names;
|
return names;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool JsonDataNode::hasChildren() {
|
bool JsonDataNode::hasChildren() {
|
||||||
return !m_children.empty();
|
if (!m_children.empty()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
// Check if m_data has any object/array values
|
||||||
|
if (m_data.is_object()) {
|
||||||
|
for (auto& [key, value] : m_data.items()) {
|
||||||
|
if (value.is_object() || value.is_array()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool JsonDataNode::hasChild(const std::string& name) const {
|
bool JsonDataNode::hasChild(const std::string& name) const {
|
||||||
return m_children.find(name) != m_children.end();
|
if (m_children.find(name) != m_children.end()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
// Check if m_data has this key as an object/array
|
||||||
|
if (m_data.is_object() && m_data.contains(name)) {
|
||||||
|
const auto& val = m_data[name];
|
||||||
|
return val.is_object() || val.is_array();
|
||||||
|
}
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ========================================
|
// ========================================
|
||||||
|
|||||||
@ -13,8 +13,20 @@ SequentialModuleSystem::SequentialModuleSystem() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
SequentialModuleSystem::~SequentialModuleSystem() {
|
SequentialModuleSystem::~SequentialModuleSystem() {
|
||||||
// Guard against logger being invalid during static destruction order
|
// IMPORTANT: During static destruction order on Windows (especially MinGW GCC 15),
|
||||||
if (logger) {
|
// spdlog's registry may be destroyed BEFORE this destructor runs.
|
||||||
|
// Using a destroyed logger causes STATUS_STACK_BUFFER_OVERRUN (0xc0000409).
|
||||||
|
// We must check if our logger is still valid before using it.
|
||||||
|
bool loggerValid = false;
|
||||||
|
try {
|
||||||
|
// Check if spdlog registry still exists and our logger is registered
|
||||||
|
loggerValid = logger && spdlog::get(logger->name()) != nullptr;
|
||||||
|
} catch (...) {
|
||||||
|
// spdlog registry may throw during destruction
|
||||||
|
loggerValid = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (loggerValid) {
|
||||||
logger->info("🔧 SequentialModuleSystem destructor called");
|
logger->info("🔧 SequentialModuleSystem destructor called");
|
||||||
|
|
||||||
if (module) {
|
if (module) {
|
||||||
|
|||||||
@ -1,5 +1,28 @@
|
|||||||
# Hot-reload test suite
|
# Hot-reload test suite
|
||||||
|
|
||||||
|
# ================================================================================
|
||||||
|
# SDL2 Detection (used by visual tests, demos, and InputModule-dependent tests)
|
||||||
|
# ================================================================================
|
||||||
|
find_package(SDL2 QUIET)
|
||||||
|
|
||||||
|
# Determine if SDL2 is available (either via find_package or system install on Linux)
|
||||||
|
set(SDL2_AVAILABLE FALSE)
|
||||||
|
if(SDL2_FOUND)
|
||||||
|
set(SDL2_AVAILABLE TRUE)
|
||||||
|
message(STATUS "SDL2 found via find_package")
|
||||||
|
elseif(UNIX AND EXISTS "/usr/include/SDL2/SDL.h")
|
||||||
|
set(SDL2_AVAILABLE TRUE)
|
||||||
|
message(STATUS "SDL2 found at system path /usr/include/SDL2")
|
||||||
|
else()
|
||||||
|
message(STATUS "SDL2 not found - visual tests and SDL2-dependent demos will be disabled")
|
||||||
|
message(STATUS " On Windows MinGW, install via: pacman -S mingw-w64-ucrt-x86_64-SDL2")
|
||||||
|
message(STATUS " On Ubuntu/Debian, install via: apt install libsdl2-dev")
|
||||||
|
endif()
|
||||||
|
|
||||||
|
# ================================================================================
|
||||||
|
# Test Modules
|
||||||
|
# ================================================================================
|
||||||
|
|
||||||
# Test module as shared library (.so) for hot-reload
|
# Test module as shared library (.so) for hot-reload
|
||||||
add_library(TestModule SHARED
|
add_library(TestModule SHARED
|
||||||
modules/TestModule.cpp
|
modules/TestModule.cpp
|
||||||
@ -678,17 +701,24 @@ if(GROVE_BUILD_BGFX_RENDERER)
|
|||||||
# CTest integration
|
# CTest integration
|
||||||
grove_add_test(BgfxRHI test_20_bgfx_rhi ${CMAKE_CURRENT_BINARY_DIR})
|
grove_add_test(BgfxRHI test_20_bgfx_rhi ${CMAKE_CURRENT_BINARY_DIR})
|
||||||
|
|
||||||
# Test 21: Visual Triangle Test (requires SDL2 and display)
|
# Test 21+: Visual Tests (requires SDL2 and display)
|
||||||
find_package(SDL2 QUIET)
|
if(SDL2_AVAILABLE)
|
||||||
if(SDL2_FOUND OR EXISTS "/usr/include/SDL2/SDL.h")
|
# Test 21: Visual Triangle Test
|
||||||
add_executable(test_21_bgfx_triangle
|
add_executable(test_21_bgfx_triangle
|
||||||
visual/test_bgfx_triangle.cpp
|
visual/test_bgfx_triangle.cpp
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if(SDL2_FOUND)
|
||||||
|
target_link_libraries(test_21_bgfx_triangle PRIVATE
|
||||||
|
bgfx
|
||||||
|
bx
|
||||||
|
SDL2::SDL2
|
||||||
|
)
|
||||||
|
else()
|
||||||
|
# Linux system SDL2
|
||||||
target_include_directories(test_21_bgfx_triangle PRIVATE
|
target_include_directories(test_21_bgfx_triangle PRIVATE
|
||||||
/usr/include/SDL2
|
/usr/include/SDL2
|
||||||
)
|
)
|
||||||
|
|
||||||
target_link_libraries(test_21_bgfx_triangle PRIVATE
|
target_link_libraries(test_21_bgfx_triangle PRIVATE
|
||||||
bgfx
|
bgfx
|
||||||
bx
|
bx
|
||||||
@ -698,19 +728,42 @@ if(GROVE_BUILD_BGFX_RENDERER)
|
|||||||
X11
|
X11
|
||||||
GL
|
GL
|
||||||
)
|
)
|
||||||
|
endif()
|
||||||
|
|
||||||
# Not added to CTest (requires display)
|
# Not added to CTest (requires display)
|
||||||
message(STATUS "Visual test 'test_21_bgfx_triangle' enabled (run manually)")
|
message(STATUS "Visual test 'test_21_bgfx_triangle' enabled (run manually)")
|
||||||
|
|
||||||
|
# Minimal bgfx test for Windows (no DLL)
|
||||||
|
if(SDL2_FOUND)
|
||||||
|
# WIN32 makes it a Windows app; SDL2main provides WinMain->main conversion
|
||||||
|
add_executable(test_bgfx_minimal_win WIN32
|
||||||
|
visual/test_bgfx_minimal_win.cpp
|
||||||
|
)
|
||||||
|
target_link_libraries(test_bgfx_minimal_win PRIVATE
|
||||||
|
bgfx
|
||||||
|
bx
|
||||||
|
SDL2::SDL2main
|
||||||
|
SDL2::SDL2
|
||||||
|
)
|
||||||
|
message(STATUS "Minimal bgfx test 'test_bgfx_minimal_win' enabled")
|
||||||
|
endif()
|
||||||
|
|
||||||
# Test 22: Sprite Integration Test (requires SDL2, display, and BgfxRenderer module)
|
# Test 22: Sprite Integration Test (requires SDL2, display, and BgfxRenderer module)
|
||||||
add_executable(test_22_bgfx_sprites
|
add_executable(test_22_bgfx_sprites
|
||||||
visual/test_bgfx_sprites.cpp
|
visual/test_bgfx_sprites.cpp
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if(SDL2_FOUND)
|
||||||
|
target_link_libraries(test_22_bgfx_sprites PRIVATE
|
||||||
|
GroveEngine::impl
|
||||||
|
bgfx
|
||||||
|
bx
|
||||||
|
SDL2::SDL2
|
||||||
|
)
|
||||||
|
else()
|
||||||
target_include_directories(test_22_bgfx_sprites PRIVATE
|
target_include_directories(test_22_bgfx_sprites PRIVATE
|
||||||
/usr/include/SDL2
|
/usr/include/SDL2
|
||||||
)
|
)
|
||||||
|
|
||||||
target_link_libraries(test_22_bgfx_sprites PRIVATE
|
target_link_libraries(test_22_bgfx_sprites PRIVATE
|
||||||
GroveEngine::impl
|
GroveEngine::impl
|
||||||
bgfx
|
bgfx
|
||||||
@ -720,6 +773,7 @@ if(GROVE_BUILD_BGFX_RENDERER)
|
|||||||
dl
|
dl
|
||||||
X11
|
X11
|
||||||
)
|
)
|
||||||
|
endif()
|
||||||
|
|
||||||
# Not added to CTest (requires display)
|
# Not added to CTest (requires display)
|
||||||
message(STATUS "Visual test 'test_22_bgfx_sprites' enabled (run manually)")
|
message(STATUS "Visual test 'test_22_bgfx_sprites' enabled (run manually)")
|
||||||
@ -729,10 +783,15 @@ if(GROVE_BUILD_BGFX_RENDERER)
|
|||||||
visual/test_23_bgfx_sprites_visual.cpp
|
visual/test_23_bgfx_sprites_visual.cpp
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if(SDL2_FOUND)
|
||||||
|
target_link_libraries(test_23_bgfx_sprites_visual PRIVATE
|
||||||
|
GroveEngine::impl
|
||||||
|
SDL2::SDL2
|
||||||
|
)
|
||||||
|
else()
|
||||||
target_include_directories(test_23_bgfx_sprites_visual PRIVATE
|
target_include_directories(test_23_bgfx_sprites_visual PRIVATE
|
||||||
/usr/include/SDL2
|
/usr/include/SDL2
|
||||||
)
|
)
|
||||||
|
|
||||||
target_link_libraries(test_23_bgfx_sprites_visual PRIVATE
|
target_link_libraries(test_23_bgfx_sprites_visual PRIVATE
|
||||||
GroveEngine::impl
|
GroveEngine::impl
|
||||||
SDL2
|
SDL2
|
||||||
@ -740,6 +799,7 @@ if(GROVE_BUILD_BGFX_RENDERER)
|
|||||||
dl
|
dl
|
||||||
X11
|
X11
|
||||||
)
|
)
|
||||||
|
endif()
|
||||||
|
|
||||||
# Not added to CTest (requires display)
|
# Not added to CTest (requires display)
|
||||||
message(STATUS "Visual test 'test_23_bgfx_sprites_visual' enabled (run manually)")
|
message(STATUS "Visual test 'test_23_bgfx_sprites_visual' enabled (run manually)")
|
||||||
@ -750,10 +810,15 @@ if(GROVE_BUILD_BGFX_RENDERER)
|
|||||||
visual/test_24_ui_basic.cpp
|
visual/test_24_ui_basic.cpp
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if(SDL2_FOUND)
|
||||||
|
target_link_libraries(test_24_ui_basic PRIVATE
|
||||||
|
GroveEngine::impl
|
||||||
|
SDL2::SDL2
|
||||||
|
)
|
||||||
|
else()
|
||||||
target_include_directories(test_24_ui_basic PRIVATE
|
target_include_directories(test_24_ui_basic PRIVATE
|
||||||
/usr/include/SDL2
|
/usr/include/SDL2
|
||||||
)
|
)
|
||||||
|
|
||||||
target_link_libraries(test_24_ui_basic PRIVATE
|
target_link_libraries(test_24_ui_basic PRIVATE
|
||||||
GroveEngine::impl
|
GroveEngine::impl
|
||||||
SDL2
|
SDL2
|
||||||
@ -761,6 +826,7 @@ if(GROVE_BUILD_BGFX_RENDERER)
|
|||||||
dl
|
dl
|
||||||
X11
|
X11
|
||||||
)
|
)
|
||||||
|
endif()
|
||||||
|
|
||||||
# Not added to CTest (requires display)
|
# Not added to CTest (requires display)
|
||||||
message(STATUS "Visual test 'test_24_ui_basic' enabled (run manually)")
|
message(STATUS "Visual test 'test_24_ui_basic' enabled (run manually)")
|
||||||
@ -770,10 +836,15 @@ if(GROVE_BUILD_BGFX_RENDERER)
|
|||||||
visual/test_25_ui_layout.cpp
|
visual/test_25_ui_layout.cpp
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if(SDL2_FOUND)
|
||||||
|
target_link_libraries(test_25_ui_layout PRIVATE
|
||||||
|
GroveEngine::impl
|
||||||
|
SDL2::SDL2
|
||||||
|
)
|
||||||
|
else()
|
||||||
target_include_directories(test_25_ui_layout PRIVATE
|
target_include_directories(test_25_ui_layout PRIVATE
|
||||||
/usr/include/SDL2
|
/usr/include/SDL2
|
||||||
)
|
)
|
||||||
|
|
||||||
target_link_libraries(test_25_ui_layout PRIVATE
|
target_link_libraries(test_25_ui_layout PRIVATE
|
||||||
GroveEngine::impl
|
GroveEngine::impl
|
||||||
SDL2
|
SDL2
|
||||||
@ -781,6 +852,7 @@ if(GROVE_BUILD_BGFX_RENDERER)
|
|||||||
dl
|
dl
|
||||||
X11
|
X11
|
||||||
)
|
)
|
||||||
|
endif()
|
||||||
|
|
||||||
# Not added to CTest (requires display)
|
# Not added to CTest (requires display)
|
||||||
message(STATUS "Visual test 'test_25_ui_layout' enabled (run manually)")
|
message(STATUS "Visual test 'test_25_ui_layout' enabled (run manually)")
|
||||||
@ -790,10 +862,15 @@ if(GROVE_BUILD_BGFX_RENDERER)
|
|||||||
visual/test_26_ui_buttons.cpp
|
visual/test_26_ui_buttons.cpp
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if(SDL2_FOUND)
|
||||||
|
target_link_libraries(test_26_ui_buttons PRIVATE
|
||||||
|
GroveEngine::impl
|
||||||
|
SDL2::SDL2
|
||||||
|
)
|
||||||
|
else()
|
||||||
target_include_directories(test_26_ui_buttons PRIVATE
|
target_include_directories(test_26_ui_buttons PRIVATE
|
||||||
/usr/include/SDL2
|
/usr/include/SDL2
|
||||||
)
|
)
|
||||||
|
|
||||||
target_link_libraries(test_26_ui_buttons PRIVATE
|
target_link_libraries(test_26_ui_buttons PRIVATE
|
||||||
GroveEngine::impl
|
GroveEngine::impl
|
||||||
SDL2
|
SDL2
|
||||||
@ -801,6 +878,7 @@ if(GROVE_BUILD_BGFX_RENDERER)
|
|||||||
dl
|
dl
|
||||||
X11
|
X11
|
||||||
)
|
)
|
||||||
|
endif()
|
||||||
|
|
||||||
# Not added to CTest (requires display)
|
# Not added to CTest (requires display)
|
||||||
message(STATUS "Visual test 'test_26_ui_buttons' enabled (run manually)")
|
message(STATUS "Visual test 'test_26_ui_buttons' enabled (run manually)")
|
||||||
@ -810,10 +888,15 @@ if(GROVE_BUILD_BGFX_RENDERER)
|
|||||||
visual/test_28_ui_scroll.cpp
|
visual/test_28_ui_scroll.cpp
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if(SDL2_FOUND)
|
||||||
|
target_link_libraries(test_28_ui_scroll PRIVATE
|
||||||
|
GroveEngine::impl
|
||||||
|
SDL2::SDL2
|
||||||
|
)
|
||||||
|
else()
|
||||||
target_include_directories(test_28_ui_scroll PRIVATE
|
target_include_directories(test_28_ui_scroll PRIVATE
|
||||||
/usr/include/SDL2
|
/usr/include/SDL2
|
||||||
)
|
)
|
||||||
|
|
||||||
target_link_libraries(test_28_ui_scroll PRIVATE
|
target_link_libraries(test_28_ui_scroll PRIVATE
|
||||||
GroveEngine::impl
|
GroveEngine::impl
|
||||||
SDL2
|
SDL2
|
||||||
@ -821,6 +904,7 @@ if(GROVE_BUILD_BGFX_RENDERER)
|
|||||||
dl
|
dl
|
||||||
X11
|
X11
|
||||||
)
|
)
|
||||||
|
endif()
|
||||||
|
|
||||||
# Not added to CTest (requires display)
|
# Not added to CTest (requires display)
|
||||||
message(STATUS "Visual test 'test_28_ui_scroll' enabled (run manually)")
|
message(STATUS "Visual test 'test_28_ui_scroll' enabled (run manually)")
|
||||||
@ -830,10 +914,15 @@ if(GROVE_BUILD_BGFX_RENDERER)
|
|||||||
visual/test_29_ui_advanced.cpp
|
visual/test_29_ui_advanced.cpp
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if(SDL2_FOUND)
|
||||||
|
target_link_libraries(test_29_ui_advanced PRIVATE
|
||||||
|
GroveEngine::impl
|
||||||
|
SDL2::SDL2
|
||||||
|
)
|
||||||
|
else()
|
||||||
target_include_directories(test_29_ui_advanced PRIVATE
|
target_include_directories(test_29_ui_advanced PRIVATE
|
||||||
/usr/include/SDL2
|
/usr/include/SDL2
|
||||||
)
|
)
|
||||||
|
|
||||||
target_link_libraries(test_29_ui_advanced PRIVATE
|
target_link_libraries(test_29_ui_advanced PRIVATE
|
||||||
GroveEngine::impl
|
GroveEngine::impl
|
||||||
SDL2
|
SDL2
|
||||||
@ -841,6 +930,7 @@ if(GROVE_BUILD_BGFX_RENDERER)
|
|||||||
dl
|
dl
|
||||||
X11
|
X11
|
||||||
)
|
)
|
||||||
|
endif()
|
||||||
|
|
||||||
# Not added to CTest (requires display)
|
# Not added to CTest (requires display)
|
||||||
message(STATUS "Visual test 'test_29_ui_advanced' enabled (run manually)")
|
message(STATUS "Visual test 'test_29_ui_advanced' enabled (run manually)")
|
||||||
@ -853,10 +943,18 @@ if(GROVE_BUILD_BGFX_RENDERER)
|
|||||||
)
|
)
|
||||||
|
|
||||||
target_include_directories(test_30_input_module PRIVATE
|
target_include_directories(test_30_input_module PRIVATE
|
||||||
/usr/include/SDL2
|
|
||||||
${CMAKE_SOURCE_DIR}/modules
|
${CMAKE_SOURCE_DIR}/modules
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if(SDL2_FOUND)
|
||||||
|
target_link_libraries(test_30_input_module PRIVATE
|
||||||
|
GroveEngine::impl
|
||||||
|
SDL2::SDL2
|
||||||
|
)
|
||||||
|
else()
|
||||||
|
target_include_directories(test_30_input_module PRIVATE
|
||||||
|
/usr/include/SDL2
|
||||||
|
)
|
||||||
target_link_libraries(test_30_input_module PRIVATE
|
target_link_libraries(test_30_input_module PRIVATE
|
||||||
GroveEngine::impl
|
GroveEngine::impl
|
||||||
SDL2
|
SDL2
|
||||||
@ -864,6 +962,7 @@ if(GROVE_BUILD_BGFX_RENDERER)
|
|||||||
dl
|
dl
|
||||||
X11
|
X11
|
||||||
)
|
)
|
||||||
|
endif()
|
||||||
|
|
||||||
# Not added to CTest (requires display and user interaction)
|
# Not added to CTest (requires display and user interaction)
|
||||||
message(STATUS "Visual test 'test_30_input_module' enabled (run manually)")
|
message(STATUS "Visual test 'test_30_input_module' enabled (run manually)")
|
||||||
@ -877,16 +976,28 @@ if(GROVE_BUILD_BGFX_RENDERER)
|
|||||||
|
|
||||||
target_include_directories(test_full_stack_interactive PRIVATE
|
target_include_directories(test_full_stack_interactive PRIVATE
|
||||||
${CMAKE_SOURCE_DIR}/modules
|
${CMAKE_SOURCE_DIR}/modules
|
||||||
|
${CMAKE_SOURCE_DIR}/modules/BgfxRenderer
|
||||||
)
|
)
|
||||||
|
|
||||||
# Platform-specific SDL2 and window system libraries
|
# Platform-specific: On Windows, link BgfxRenderer statically (bgfx DLL issues)
|
||||||
if(WIN32)
|
if(WIN32)
|
||||||
|
# Static linking for BgfxRenderer - required on Windows
|
||||||
|
# bgfx crashes when loaded from DLL due to threading/TLS issues
|
||||||
|
target_compile_definitions(test_full_stack_interactive PRIVATE USE_STATIC_BGFX)
|
||||||
target_link_libraries(test_full_stack_interactive PRIVATE
|
target_link_libraries(test_full_stack_interactive PRIVATE
|
||||||
GroveEngine::impl
|
GroveEngine::impl
|
||||||
|
BgfxRenderer_static
|
||||||
SDL2::SDL2
|
SDL2::SDL2
|
||||||
spdlog::spdlog
|
spdlog::spdlog
|
||||||
)
|
)
|
||||||
|
# MSVC: Use console subsystem for main() entry point
|
||||||
|
if(MSVC)
|
||||||
|
set_target_properties(test_full_stack_interactive PROPERTIES
|
||||||
|
LINK_FLAGS "/SUBSYSTEM:CONSOLE"
|
||||||
|
)
|
||||||
|
endif()
|
||||||
else()
|
else()
|
||||||
|
# Dynamic linking on Linux/Mac - works fine
|
||||||
target_include_directories(test_full_stack_interactive PRIVATE
|
target_include_directories(test_full_stack_interactive PRIVATE
|
||||||
/usr/include/SDL2
|
/usr/include/SDL2
|
||||||
)
|
)
|
||||||
@ -1192,11 +1303,18 @@ message(STATUS "Integration test 'IT_015_input_ui_integration_minimal' enabled (
|
|||||||
# UIModule Interactive Showcase Demo
|
# UIModule Interactive Showcase Demo
|
||||||
# ============================================
|
# ============================================
|
||||||
|
|
||||||
if(GROVE_BUILD_UI_MODULE AND GROVE_BUILD_BGFX_RENDERER)
|
if(GROVE_BUILD_UI_MODULE AND GROVE_BUILD_BGFX_RENDERER AND SDL2_AVAILABLE)
|
||||||
add_executable(demo_ui_showcase
|
add_executable(demo_ui_showcase
|
||||||
demo/demo_ui_showcase.cpp
|
demo/demo_ui_showcase.cpp
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if(SDL2_FOUND)
|
||||||
|
target_link_libraries(demo_ui_showcase PRIVATE
|
||||||
|
GroveEngine::core
|
||||||
|
GroveEngine::impl
|
||||||
|
SDL2::SDL2
|
||||||
|
)
|
||||||
|
else()
|
||||||
target_link_libraries(demo_ui_showcase PRIVATE
|
target_link_libraries(demo_ui_showcase PRIVATE
|
||||||
GroveEngine::core
|
GroveEngine::core
|
||||||
GroveEngine::impl
|
GroveEngine::impl
|
||||||
@ -1204,11 +1322,132 @@ if(GROVE_BUILD_UI_MODULE AND GROVE_BUILD_BGFX_RENDERER)
|
|||||||
pthread
|
pthread
|
||||||
dl
|
dl
|
||||||
)
|
)
|
||||||
|
|
||||||
# Add X11 on Linux for SDL window integration
|
# Add X11 on Linux for SDL window integration
|
||||||
if(UNIX AND NOT APPLE)
|
if(UNIX AND NOT APPLE)
|
||||||
target_link_libraries(demo_ui_showcase PRIVATE X11)
|
target_link_libraries(demo_ui_showcase PRIVATE X11)
|
||||||
endif()
|
endif()
|
||||||
|
endif()
|
||||||
|
|
||||||
message(STATUS "UIModule showcase demo 'demo_ui_showcase' enabled")
|
message(STATUS "UIModule showcase demo 'demo_ui_showcase' enabled")
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
# Minimal test using BgfxRenderer_static only (no DLL loading)
|
||||||
|
if(WIN32 AND GROVE_BUILD_BGFX_RENDERER)
|
||||||
|
add_executable(test_bgfx_static_only WIN32
|
||||||
|
visual/test_bgfx_static_only.cpp
|
||||||
|
)
|
||||||
|
target_include_directories(test_bgfx_static_only PRIVATE
|
||||||
|
${CMAKE_SOURCE_DIR}/modules
|
||||||
|
${CMAKE_SOURCE_DIR}/modules/BgfxRenderer
|
||||||
|
)
|
||||||
|
target_link_libraries(test_bgfx_static_only PRIVATE
|
||||||
|
SDL2::SDL2main
|
||||||
|
SDL2::SDL2
|
||||||
|
GroveEngine::impl
|
||||||
|
BgfxRenderer_static
|
||||||
|
spdlog::spdlog
|
||||||
|
)
|
||||||
|
message(STATUS "Minimal BgfxRenderer_static test 'test_bgfx_static_only' enabled")
|
||||||
|
endif()
|
||||||
|
|
||||||
|
# Test using BgfxDevice directly (no BgfxRendererModule)
|
||||||
|
if(WIN32 AND GROVE_BUILD_BGFX_RENDERER)
|
||||||
|
add_executable(test_bgfx_device_only WIN32
|
||||||
|
visual/test_bgfx_device_only.cpp
|
||||||
|
)
|
||||||
|
target_include_directories(test_bgfx_device_only PRIVATE
|
||||||
|
${CMAKE_SOURCE_DIR}/modules
|
||||||
|
${CMAKE_SOURCE_DIR}/modules/BgfxRenderer
|
||||||
|
)
|
||||||
|
target_link_libraries(test_bgfx_device_only PRIVATE
|
||||||
|
SDL2::SDL2main
|
||||||
|
SDL2::SDL2
|
||||||
|
BgfxRenderer_static
|
||||||
|
spdlog::spdlog
|
||||||
|
)
|
||||||
|
message(STATUS "BgfxDevice direct test 'test_bgfx_device_only' enabled")
|
||||||
|
endif()
|
||||||
|
|
||||||
|
# ================================================================================
|
||||||
|
# Complete Showcases (Renderer and UI demonstrations)
|
||||||
|
# ================================================================================
|
||||||
|
|
||||||
|
# BgfxRenderer Complete Showcase - all rendering features
|
||||||
|
if(WIN32 AND GROVE_BUILD_BGFX_RENDERER AND SDL2_AVAILABLE)
|
||||||
|
add_executable(test_renderer_showcase WIN32
|
||||||
|
visual/test_renderer_showcase.cpp
|
||||||
|
)
|
||||||
|
target_include_directories(test_renderer_showcase PRIVATE
|
||||||
|
${CMAKE_SOURCE_DIR}/modules
|
||||||
|
${CMAKE_SOURCE_DIR}/modules/BgfxRenderer
|
||||||
|
)
|
||||||
|
target_link_libraries(test_renderer_showcase PRIVATE
|
||||||
|
SDL2::SDL2main
|
||||||
|
SDL2::SDL2
|
||||||
|
GroveEngine::impl
|
||||||
|
BgfxRenderer_static
|
||||||
|
spdlog::spdlog
|
||||||
|
)
|
||||||
|
message(STATUS "BgfxRenderer showcase 'test_renderer_showcase' enabled")
|
||||||
|
endif()
|
||||||
|
|
||||||
|
# UIModule Complete Showcase - all UI widgets
|
||||||
|
if(WIN32 AND GROVE_BUILD_BGFX_RENDERER AND GROVE_BUILD_UI_MODULE AND SDL2_AVAILABLE)
|
||||||
|
add_executable(test_ui_showcase WIN32
|
||||||
|
visual/test_ui_showcase.cpp
|
||||||
|
)
|
||||||
|
target_include_directories(test_ui_showcase PRIVATE
|
||||||
|
${CMAKE_SOURCE_DIR}/modules
|
||||||
|
${CMAKE_SOURCE_DIR}/modules/BgfxRenderer
|
||||||
|
${CMAKE_SOURCE_DIR}/modules/UIModule
|
||||||
|
)
|
||||||
|
target_link_libraries(test_ui_showcase PRIVATE
|
||||||
|
SDL2::SDL2main
|
||||||
|
SDL2::SDL2
|
||||||
|
GroveEngine::impl
|
||||||
|
BgfxRenderer_static
|
||||||
|
UIModule_static
|
||||||
|
spdlog::spdlog
|
||||||
|
)
|
||||||
|
message(STATUS "UIModule showcase 'test_ui_showcase' enabled")
|
||||||
|
endif()
|
||||||
|
|
||||||
|
# Sprite Rendering Diagnostic Test - isolates IIO sprite pipeline
|
||||||
|
if(WIN32 AND GROVE_BUILD_BGFX_RENDERER AND SDL2_AVAILABLE)
|
||||||
|
add_executable(test_sprite_debug WIN32
|
||||||
|
visual/test_sprite_debug.cpp
|
||||||
|
)
|
||||||
|
target_include_directories(test_sprite_debug PRIVATE
|
||||||
|
${CMAKE_SOURCE_DIR}/modules
|
||||||
|
${CMAKE_SOURCE_DIR}/modules/BgfxRenderer
|
||||||
|
)
|
||||||
|
target_link_libraries(test_sprite_debug PRIVATE
|
||||||
|
SDL2::SDL2main
|
||||||
|
SDL2::SDL2
|
||||||
|
GroveEngine::impl
|
||||||
|
BgfxRenderer_static
|
||||||
|
spdlog::spdlog
|
||||||
|
)
|
||||||
|
message(STATUS "Sprite diagnostic 'test_sprite_debug' enabled")
|
||||||
|
endif()
|
||||||
|
|
||||||
|
# Single Button Test - one UIButton only
|
||||||
|
if(WIN32 AND GROVE_BUILD_BGFX_RENDERER AND GROVE_BUILD_UI_MODULE AND SDL2_AVAILABLE)
|
||||||
|
add_executable(test_single_button WIN32
|
||||||
|
visual/test_single_button.cpp
|
||||||
|
)
|
||||||
|
target_include_directories(test_single_button PRIVATE
|
||||||
|
${CMAKE_SOURCE_DIR}/modules
|
||||||
|
${CMAKE_SOURCE_DIR}/modules/BgfxRenderer
|
||||||
|
${CMAKE_SOURCE_DIR}/modules/UIModule
|
||||||
|
)
|
||||||
|
target_link_libraries(test_single_button PRIVATE
|
||||||
|
SDL2::SDL2main
|
||||||
|
SDL2::SDL2
|
||||||
|
GroveEngine::impl
|
||||||
|
BgfxRenderer_static
|
||||||
|
UIModule_static
|
||||||
|
spdlog::spdlog
|
||||||
|
)
|
||||||
|
message(STATUS "Single button test 'test_single_button' enabled")
|
||||||
|
endif()
|
||||||
|
|||||||
90
tests/visual/test_bgfx_device_only.cpp
Normal file
90
tests/visual/test_bgfx_device_only.cpp
Normal file
@ -0,0 +1,90 @@
|
|||||||
|
/**
|
||||||
|
* Test using BgfxDevice directly via IRHIDevice::create()
|
||||||
|
* No ShaderManager, no RenderGraph - just init + frame
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <SDL.h>
|
||||||
|
#include <SDL_syswm.h>
|
||||||
|
#include <iostream>
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
#include "RHI/RHIDevice.h"
|
||||||
|
|
||||||
|
#include <spdlog/spdlog.h>
|
||||||
|
#include <spdlog/sinks/stdout_color_sinks.h>
|
||||||
|
|
||||||
|
using namespace grove::rhi;
|
||||||
|
|
||||||
|
int main(int argc, char* argv[]) {
|
||||||
|
auto logger = spdlog::stdout_color_mt("Main");
|
||||||
|
spdlog::set_level(spdlog::level::info);
|
||||||
|
|
||||||
|
logger->info("=== BgfxDevice Direct Test ===");
|
||||||
|
|
||||||
|
if (SDL_Init(SDL_INIT_VIDEO) < 0) {
|
||||||
|
logger->error("SDL_Init failed: {}", SDL_GetError());
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
SDL_Window* window = SDL_CreateWindow(
|
||||||
|
"BgfxDevice Direct Test",
|
||||||
|
SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED,
|
||||||
|
800, 600,
|
||||||
|
SDL_WINDOW_SHOWN
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!window) {
|
||||||
|
logger->error("SDL_CreateWindow failed");
|
||||||
|
SDL_Quit();
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
SDL_SysWMinfo wmi;
|
||||||
|
SDL_VERSION(&wmi.version);
|
||||||
|
SDL_GetWindowWMInfo(window, &wmi);
|
||||||
|
|
||||||
|
logger->info("Window handle: {}", (void*)wmi.info.win.window);
|
||||||
|
|
||||||
|
// Create BgfxDevice via factory
|
||||||
|
auto device = IRHIDevice::create();
|
||||||
|
|
||||||
|
logger->info("Initializing BgfxDevice...");
|
||||||
|
if (!device->init(wmi.info.win.window, nullptr, 800, 600)) {
|
||||||
|
logger->error("BgfxDevice init failed");
|
||||||
|
SDL_DestroyWindow(window);
|
||||||
|
SDL_Quit();
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
logger->info("BgfxDevice initialized, starting loop...");
|
||||||
|
|
||||||
|
bool running = true;
|
||||||
|
int frameCount = 0;
|
||||||
|
|
||||||
|
while (running && frameCount < 60) {
|
||||||
|
logger->info("Frame {} start", frameCount);
|
||||||
|
spdlog::default_logger()->flush();
|
||||||
|
|
||||||
|
SDL_Event e;
|
||||||
|
while (SDL_PollEvent(&e)) {
|
||||||
|
if (e.type == SDL_QUIT || (e.type == SDL_KEYDOWN && e.key.keysym.sym == SDLK_ESCAPE)) {
|
||||||
|
running = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Just frame() - exactly like test_bgfx_minimal_win
|
||||||
|
device->frame();
|
||||||
|
|
||||||
|
logger->info("Frame {} complete", frameCount);
|
||||||
|
frameCount++;
|
||||||
|
}
|
||||||
|
|
||||||
|
logger->info("Rendered {} frames", frameCount);
|
||||||
|
|
||||||
|
device->shutdown();
|
||||||
|
SDL_DestroyWindow(window);
|
||||||
|
SDL_Quit();
|
||||||
|
|
||||||
|
logger->info("=== Test complete ===");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
91
tests/visual/test_bgfx_minimal_win.cpp
Normal file
91
tests/visual/test_bgfx_minimal_win.cpp
Normal file
@ -0,0 +1,91 @@
|
|||||||
|
/**
|
||||||
|
* Minimal bgfx test for Windows - no DLL, just renders a red screen
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <SDL.h>
|
||||||
|
#include <SDL_syswm.h>
|
||||||
|
#include <bgfx/bgfx.h>
|
||||||
|
#include <bgfx/platform.h>
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
int main(int argc, char* argv[]) {
|
||||||
|
std::cout << "=== Minimal bgfx test ===\n";
|
||||||
|
|
||||||
|
if (SDL_Init(SDL_INIT_VIDEO) < 0) {
|
||||||
|
std::cerr << "SDL_Init failed: " << SDL_GetError() << "\n";
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
SDL_Window* window = SDL_CreateWindow(
|
||||||
|
"bgfx minimal test",
|
||||||
|
SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED,
|
||||||
|
800, 600,
|
||||||
|
SDL_WINDOW_SHOWN
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!window) {
|
||||||
|
std::cerr << "SDL_CreateWindow failed\n";
|
||||||
|
SDL_Quit();
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get native window handle
|
||||||
|
SDL_SysWMinfo wmi;
|
||||||
|
SDL_VERSION(&wmi.version);
|
||||||
|
SDL_GetWindowWMInfo(window, &wmi);
|
||||||
|
|
||||||
|
// Setup bgfx
|
||||||
|
bgfx::Init init;
|
||||||
|
init.type = bgfx::RendererType::Direct3D11;
|
||||||
|
init.resolution.width = 800;
|
||||||
|
init.resolution.height = 600;
|
||||||
|
init.resolution.reset = BGFX_RESET_VSYNC;
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
|
init.platformData.nwh = wmi.info.win.window;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
std::cout << "Initializing bgfx with D3D11...\n";
|
||||||
|
|
||||||
|
if (!bgfx::init(init)) {
|
||||||
|
std::cerr << "bgfx::init failed\n";
|
||||||
|
SDL_DestroyWindow(window);
|
||||||
|
SDL_Quit();
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
const bgfx::Caps* caps = bgfx::getCaps();
|
||||||
|
std::cout << "Renderer: " << bgfx::getRendererName(caps->rendererType) << "\n";
|
||||||
|
|
||||||
|
// Set bright red clear color
|
||||||
|
bgfx::setViewClear(0, BGFX_CLEAR_COLOR | BGFX_CLEAR_DEPTH, 0xFF0000FF, 1.0f, 0);
|
||||||
|
bgfx::setViewRect(0, 0, 0, 800, 600);
|
||||||
|
|
||||||
|
std::cout << "Running for 3 seconds...\n";
|
||||||
|
|
||||||
|
Uint32 start = SDL_GetTicks();
|
||||||
|
bool running = true;
|
||||||
|
int frames = 0;
|
||||||
|
|
||||||
|
while (running && (SDL_GetTicks() - start) < 3000) {
|
||||||
|
SDL_Event e;
|
||||||
|
while (SDL_PollEvent(&e)) {
|
||||||
|
if (e.type == SDL_QUIT || (e.type == SDL_KEYDOWN && e.key.keysym.sym == SDLK_ESCAPE)) {
|
||||||
|
running = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bgfx::touch(0);
|
||||||
|
bgfx::frame();
|
||||||
|
frames++;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::cout << "Rendered " << frames << " frames\n";
|
||||||
|
|
||||||
|
bgfx::shutdown();
|
||||||
|
SDL_DestroyWindow(window);
|
||||||
|
SDL_Quit();
|
||||||
|
|
||||||
|
std::cout << "=== Test complete ===\n";
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
188
tests/visual/test_bgfx_static_only.cpp
Normal file
188
tests/visual/test_bgfx_static_only.cpp
Normal file
@ -0,0 +1,188 @@
|
|||||||
|
/**
|
||||||
|
* Minimal test using BgfxRenderer_static - no DLLs loaded
|
||||||
|
* Tests if BgfxRendererModule works in isolation
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <SDL.h>
|
||||||
|
#include <SDL_syswm.h>
|
||||||
|
#include <iostream>
|
||||||
|
#include <cmath>
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
#include "BgfxRendererModule.h"
|
||||||
|
#include <grove/JsonDataNode.h>
|
||||||
|
#include <grove/IntraIOManager.h>
|
||||||
|
#include <grove/IntraIO.h>
|
||||||
|
#include <spdlog/spdlog.h>
|
||||||
|
#include <spdlog/sinks/stdout_color_sinks.h>
|
||||||
|
|
||||||
|
using namespace grove;
|
||||||
|
|
||||||
|
int main(int argc, char* argv[]) {
|
||||||
|
// Setup logging
|
||||||
|
auto logger = spdlog::stdout_color_mt("Main");
|
||||||
|
spdlog::set_level(spdlog::level::info);
|
||||||
|
|
||||||
|
logger->info("=== BgfxRenderer Static Only Test ===");
|
||||||
|
|
||||||
|
if (SDL_Init(SDL_INIT_VIDEO) < 0) {
|
||||||
|
logger->error("SDL_Init failed: {}", SDL_GetError());
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
SDL_Window* window = SDL_CreateWindow(
|
||||||
|
"BgfxRenderer Static Test",
|
||||||
|
SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED,
|
||||||
|
800, 600,
|
||||||
|
SDL_WINDOW_SHOWN
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!window) {
|
||||||
|
logger->error("SDL_CreateWindow failed: {}", SDL_GetError());
|
||||||
|
SDL_Quit();
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get native window handle
|
||||||
|
SDL_SysWMinfo wmi;
|
||||||
|
SDL_VERSION(&wmi.version);
|
||||||
|
SDL_GetWindowWMInfo(window, &wmi);
|
||||||
|
|
||||||
|
logger->info("Window created, handle: {}", (void*)wmi.info.win.window);
|
||||||
|
|
||||||
|
// Create IO instances from singleton manager
|
||||||
|
// NOTE: Must use singleton because IntraIO::publish/subscribe use IntraIOManager::getInstance()
|
||||||
|
// IMPORTANT: Need SEPARATE instances for publisher and subscriber because messages
|
||||||
|
// are not delivered back to the sender (see IntraIOManager::routeMessage)
|
||||||
|
auto rendererIOPtr = IntraIOManager::getInstance().createInstance("renderer");
|
||||||
|
IIO* rendererIO = rendererIOPtr.get();
|
||||||
|
|
||||||
|
// Separate "game" instance for publishing render commands
|
||||||
|
auto gameIOPtr = IntraIOManager::getInstance().createInstance("game");
|
||||||
|
IIO* gameIO = gameIOPtr.get();
|
||||||
|
|
||||||
|
// Create and configure renderer
|
||||||
|
auto renderer = std::make_unique<BgfxRendererModule>();
|
||||||
|
|
||||||
|
JsonDataNode config("config");
|
||||||
|
config.setDouble("nativeWindowHandle", static_cast<double>(reinterpret_cast<uintptr_t>(wmi.info.win.window)));
|
||||||
|
config.setInt("windowWidth", 800);
|
||||||
|
config.setInt("windowHeight", 600);
|
||||||
|
config.setString("backend", "d3d11");
|
||||||
|
config.setString("shaderPath", "./shaders");
|
||||||
|
config.setBool("vsync", true);
|
||||||
|
|
||||||
|
logger->info("Configuring BgfxRendererModule...");
|
||||||
|
renderer->setConfiguration(config, rendererIO, nullptr);
|
||||||
|
|
||||||
|
logger->info("BgfxRendererModule configured");
|
||||||
|
|
||||||
|
// Main loop
|
||||||
|
logger->info("Starting main loop...");
|
||||||
|
bool running = true;
|
||||||
|
int frameCount = 0;
|
||||||
|
Uint64 start = SDL_GetPerformanceCounter();
|
||||||
|
|
||||||
|
// Setup camera for orthographic projection (required for pixel-space rendering)
|
||||||
|
{
|
||||||
|
auto cam = std::make_unique<JsonDataNode>("camera");
|
||||||
|
cam->setDouble("x", 0.0);
|
||||||
|
cam->setDouble("y", 0.0);
|
||||||
|
cam->setDouble("zoom", 1.0);
|
||||||
|
cam->setInt("viewportX", 0);
|
||||||
|
cam->setInt("viewportY", 0);
|
||||||
|
cam->setInt("viewportW", 800);
|
||||||
|
cam->setInt("viewportH", 600);
|
||||||
|
gameIO->publish("render:camera", std::move(cam));
|
||||||
|
}
|
||||||
|
|
||||||
|
while (running && frameCount < 300) { // Run for 300 frames (~5 sec)
|
||||||
|
SDL_Event e;
|
||||||
|
while (SDL_PollEvent(&e)) {
|
||||||
|
if (e.type == SDL_QUIT || (e.type == SDL_KEYDOWN && e.key.keysym.sym == SDLK_ESCAPE)) {
|
||||||
|
running = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send render commands via IIO before processing
|
||||||
|
// Draw debug rectangles (colored boxes without texture)
|
||||||
|
float t = frameCount * 0.02f;
|
||||||
|
|
||||||
|
// Moving red rectangle
|
||||||
|
{
|
||||||
|
auto rect = std::make_unique<JsonDataNode>("rect");
|
||||||
|
rect->setDouble("x", 100 + std::sin(t) * 50);
|
||||||
|
rect->setDouble("y", 100);
|
||||||
|
rect->setDouble("width", 100);
|
||||||
|
rect->setDouble("height", 100);
|
||||||
|
rect->setInt("color", 0xFF0000FF); // Red, alpha=255
|
||||||
|
rect->setBool("filled", true);
|
||||||
|
gameIO->publish("render:debug:rect", std::move(rect));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Moving green rectangle
|
||||||
|
{
|
||||||
|
auto rect = std::make_unique<JsonDataNode>("rect");
|
||||||
|
rect->setDouble("x", 300);
|
||||||
|
rect->setDouble("y", 200 + std::cos(t) * 50);
|
||||||
|
rect->setDouble("width", 80);
|
||||||
|
rect->setDouble("height", 80);
|
||||||
|
rect->setInt("color", 0x00FF00FF); // Green
|
||||||
|
rect->setBool("filled", true);
|
||||||
|
gameIO->publish("render:debug:rect", std::move(rect));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Rotating blue rectangle
|
||||||
|
{
|
||||||
|
auto rect = std::make_unique<JsonDataNode>("rect");
|
||||||
|
rect->setDouble("x", 500);
|
||||||
|
rect->setDouble("y", 300);
|
||||||
|
rect->setDouble("width", 120);
|
||||||
|
rect->setDouble("height", 60);
|
||||||
|
rect->setInt("color", 0x0088FFFF); // Blue
|
||||||
|
rect->setBool("filled", true);
|
||||||
|
gameIO->publish("render:debug:rect", std::move(rect));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Draw debug lines
|
||||||
|
{
|
||||||
|
auto line = std::make_unique<JsonDataNode>("line");
|
||||||
|
line->setDouble("x1", 50);
|
||||||
|
line->setDouble("y1", 50);
|
||||||
|
line->setDouble("x2", 750);
|
||||||
|
line->setDouble("y2", 550);
|
||||||
|
line->setInt("color", 0xFFFF00FF); // Yellow
|
||||||
|
gameIO->publish("render:debug:line", std::move(line));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Process renderer
|
||||||
|
JsonDataNode input("input");
|
||||||
|
input.setDouble("deltaTime", 0.016);
|
||||||
|
input.setInt("frameCount", frameCount);
|
||||||
|
|
||||||
|
renderer->process(input);
|
||||||
|
|
||||||
|
frameCount++;
|
||||||
|
|
||||||
|
// Log every 60 frames
|
||||||
|
if (frameCount % 60 == 0) {
|
||||||
|
logger->info("Frame {}", frameCount);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Uint64 end = SDL_GetPerformanceCounter();
|
||||||
|
double elapsed = (end - start) / (double)SDL_GetPerformanceFrequency();
|
||||||
|
|
||||||
|
logger->info("Rendered {} frames in {:.2f}s ({:.1f} FPS)", frameCount, elapsed, frameCount / elapsed);
|
||||||
|
|
||||||
|
// Cleanup
|
||||||
|
logger->info("Shutting down...");
|
||||||
|
renderer->shutdown();
|
||||||
|
IntraIOManager::getInstance().removeInstance("renderer");
|
||||||
|
IntraIOManager::getInstance().removeInstance("game");
|
||||||
|
SDL_DestroyWindow(window);
|
||||||
|
SDL_Quit();
|
||||||
|
|
||||||
|
logger->info("=== Test complete ===");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
@ -11,6 +11,10 @@
|
|||||||
* - Mouse: Click buttons, drag sliders
|
* - Mouse: Click buttons, drag sliders
|
||||||
* - Keyboard: Type in text input, press Space to spawn sprites
|
* - Keyboard: Type in text input, press Space to spawn sprites
|
||||||
* - ESC: Exit
|
* - ESC: Exit
|
||||||
|
*
|
||||||
|
* Build modes:
|
||||||
|
* - USE_STATIC_BGFX: Link BgfxRenderer statically (required on Windows)
|
||||||
|
* - Default: Load BgfxRenderer as DLL (works on Linux/Mac)
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include <grove/ModuleLoader.h>
|
#include <grove/ModuleLoader.h>
|
||||||
@ -34,6 +38,11 @@
|
|||||||
#include <dlfcn.h>
|
#include <dlfcn.h>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
// Static linking for BgfxRenderer (required on Windows due to bgfx DLL issues)
|
||||||
|
#ifdef USE_STATIC_BGFX
|
||||||
|
#include "BgfxRendererModule.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
// Function pointer type for feedEvent (loaded from DLL)
|
// Function pointer type for feedEvent (loaded from DLL)
|
||||||
typedef void (*FeedEventFunc)(grove::IModule*, const void*);
|
typedef void (*FeedEventFunc)(grove::IModule*, const void*);
|
||||||
|
|
||||||
@ -64,9 +73,9 @@ public:
|
|||||||
sprite.x += sprite.vx * deltaTime;
|
sprite.x += sprite.vx * deltaTime;
|
||||||
sprite.y += sprite.vy * deltaTime;
|
sprite.y += sprite.vy * deltaTime;
|
||||||
|
|
||||||
// Bounce off walls
|
// Bounce off walls (1280x720 window)
|
||||||
if (sprite.x < 0 || sprite.x > 1920) sprite.vx = -sprite.vx;
|
if (sprite.x < 0 || sprite.x > 1280) sprite.vx = -sprite.vx;
|
||||||
if (sprite.y < 0 || sprite.y > 1080) sprite.vy = -sprite.vy;
|
if (sprite.y < 0 || sprite.y > 720) sprite.vy = -sprite.vy;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Process events
|
// Process events
|
||||||
@ -201,11 +210,13 @@ int main(int argc, char* argv[]) {
|
|||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create window
|
// Create window (windowed mode, not fullscreen)
|
||||||
|
const int WINDOW_WIDTH = 1280;
|
||||||
|
const int WINDOW_HEIGHT = 720;
|
||||||
SDL_Window* window = SDL_CreateWindow(
|
SDL_Window* window = SDL_CreateWindow(
|
||||||
"GroveEngine - Full Stack Demo",
|
"GroveEngine - Full Stack Demo",
|
||||||
SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED,
|
SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED,
|
||||||
1920, 1080,
|
WINDOW_WIDTH, WINDOW_HEIGHT,
|
||||||
SDL_WINDOW_SHOWN | SDL_WINDOW_RESIZABLE
|
SDL_WINDOW_SHOWN | SDL_WINDOW_RESIZABLE
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -239,37 +250,49 @@ int main(int argc, char* argv[]) {
|
|||||||
auto gameIO = ioManager.createInstance("game");
|
auto gameIO = ioManager.createInstance("game");
|
||||||
|
|
||||||
// Load modules
|
// Load modules
|
||||||
ModuleLoader rendererLoader, uiLoader, inputLoader;
|
ModuleLoader uiLoader, inputLoader;
|
||||||
|
#ifndef USE_STATIC_BGFX
|
||||||
|
ModuleLoader rendererLoader;
|
||||||
|
#endif
|
||||||
|
|
||||||
std::string rendererPath = "./modules/BgfxRenderer.dll";
|
|
||||||
std::string uiPath = "./modules/UIModule.dll";
|
std::string uiPath = "./modules/UIModule.dll";
|
||||||
std::string inputPath = "./modules/InputModule.dll";
|
std::string inputPath = "./modules/InputModule.dll";
|
||||||
|
|
||||||
#ifndef _WIN32
|
#ifndef _WIN32
|
||||||
rendererPath = "./modules/libBgfxRenderer.so";
|
|
||||||
uiPath = "./modules/libUIModule.so";
|
uiPath = "./modules/libUIModule.so";
|
||||||
inputPath = "./modules/libInputModule.so";
|
inputPath = "./modules/libInputModule.so";
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
logger->info("Loading modules...");
|
logger->info("Loading modules...");
|
||||||
|
|
||||||
// Load BgfxRenderer
|
// Load/Create BgfxRenderer
|
||||||
std::unique_ptr<IModule> renderer;
|
std::unique_ptr<IModule> renderer;
|
||||||
|
#ifdef USE_STATIC_BGFX
|
||||||
|
// Static linking: instantiate directly (required on Windows)
|
||||||
|
renderer = std::make_unique<BgfxRendererModule>();
|
||||||
|
logger->info("✅ BgfxRenderer created (static)");
|
||||||
|
#else
|
||||||
|
// Dynamic linking: load from DLL
|
||||||
|
std::string rendererPath = "./modules/BgfxRenderer.dll";
|
||||||
|
#ifndef _WIN32
|
||||||
|
rendererPath = "./modules/libBgfxRenderer.so";
|
||||||
|
#endif
|
||||||
try {
|
try {
|
||||||
renderer = rendererLoader.load(rendererPath, "renderer");
|
renderer = rendererLoader.load(rendererPath, "renderer");
|
||||||
logger->info("✅ BgfxRenderer loaded");
|
logger->info("✅ BgfxRenderer loaded (dynamic)");
|
||||||
} catch (const std::exception& e) {
|
} catch (const std::exception& e) {
|
||||||
logger->error("Failed to load BgfxRenderer: {}", e.what());
|
logger->error("Failed to load BgfxRenderer: {}", e.what());
|
||||||
SDL_DestroyWindow(window);
|
SDL_DestroyWindow(window);
|
||||||
SDL_Quit();
|
SDL_Quit();
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
// Configure BgfxRenderer
|
// Configure BgfxRenderer
|
||||||
JsonDataNode rendererConfig("config");
|
JsonDataNode rendererConfig("config");
|
||||||
rendererConfig.setInt("windowWidth", 1920);
|
rendererConfig.setInt("windowWidth", WINDOW_WIDTH);
|
||||||
rendererConfig.setInt("windowHeight", 1080);
|
rendererConfig.setInt("windowHeight", WINDOW_HEIGHT);
|
||||||
rendererConfig.setString("backend", "auto");
|
rendererConfig.setString("backend", "opengl"); // Force OpenGL instead of D3D11
|
||||||
rendererConfig.setBool("vsync", true);
|
rendererConfig.setBool("vsync", true);
|
||||||
rendererConfig.setInt("nativeWindowHandle", (int)(intptr_t)nativeHandle);
|
rendererConfig.setInt("nativeWindowHandle", (int)(intptr_t)nativeHandle);
|
||||||
renderer->setConfiguration(rendererConfig, rendererIO.get(), nullptr);
|
renderer->setConfiguration(rendererConfig, rendererIO.get(), nullptr);
|
||||||
@ -289,8 +312,8 @@ int main(int argc, char* argv[]) {
|
|||||||
|
|
||||||
// Configure UIModule with inline layout
|
// Configure UIModule with inline layout
|
||||||
JsonDataNode uiConfig("config");
|
JsonDataNode uiConfig("config");
|
||||||
uiConfig.setInt("windowWidth", 1920);
|
uiConfig.setInt("windowWidth", WINDOW_WIDTH);
|
||||||
uiConfig.setInt("windowHeight", 1080);
|
uiConfig.setInt("windowHeight", WINDOW_HEIGHT);
|
||||||
uiConfig.setInt("baseLayer", 1000);
|
uiConfig.setInt("baseLayer", 1000);
|
||||||
|
|
||||||
// Create inline layout
|
// Create inline layout
|
||||||
@ -456,11 +479,18 @@ int main(int argc, char* argv[]) {
|
|||||||
int frameCount = 0;
|
int frameCount = 0;
|
||||||
|
|
||||||
logger->info("Entering main loop...");
|
logger->info("Entering main loop...");
|
||||||
|
spdlog::default_logger()->flush();
|
||||||
|
|
||||||
while (running) {
|
while (running) {
|
||||||
|
logger->info("Frame {} start", frameCount);
|
||||||
|
spdlog::default_logger()->flush();
|
||||||
|
logger->info(" SDL_PollEvent...");
|
||||||
|
spdlog::default_logger()->flush();
|
||||||
// Handle SDL events
|
// Handle SDL events
|
||||||
SDL_Event event;
|
SDL_Event event;
|
||||||
while (SDL_PollEvent(&event)) {
|
while (SDL_PollEvent(&event)) {
|
||||||
|
logger->info(" Event type: {}", event.type);
|
||||||
|
spdlog::default_logger()->flush();
|
||||||
if (event.type == SDL_QUIT) {
|
if (event.type == SDL_QUIT) {
|
||||||
running = false;
|
running = false;
|
||||||
}
|
}
|
||||||
@ -469,6 +499,8 @@ int main(int argc, char* argv[]) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Feed to InputModule via exported C function
|
// Feed to InputModule via exported C function
|
||||||
|
logger->info(" feedEventFunc...");
|
||||||
|
spdlog::default_logger()->flush();
|
||||||
feedEventFunc(inputModuleBase.get(), &event);
|
feedEventFunc(inputModuleBase.get(), &event);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -485,11 +517,18 @@ int main(int argc, char* argv[]) {
|
|||||||
input.setDouble("deltaTime", deltaTime);
|
input.setDouble("deltaTime", deltaTime);
|
||||||
input.setInt("frameCount", frameCount);
|
input.setInt("frameCount", frameCount);
|
||||||
|
|
||||||
|
logger->info("Processing input module...");
|
||||||
inputModuleBase->process(input);
|
inputModuleBase->process(input);
|
||||||
|
logger->info("Processing UI module...");
|
||||||
uiModule->process(input);
|
uiModule->process(input);
|
||||||
|
logger->info("Updating game logic...");
|
||||||
gameLogic.update((float)deltaTime);
|
gameLogic.update((float)deltaTime);
|
||||||
|
logger->info("Rendering game...");
|
||||||
gameLogic.render(rendererIO.get());
|
gameLogic.render(rendererIO.get());
|
||||||
|
logger->info("Processing renderer...");
|
||||||
|
spdlog::default_logger()->flush();
|
||||||
renderer->process(input);
|
renderer->process(input);
|
||||||
|
logger->info("Frame {} complete", frameCount);
|
||||||
|
|
||||||
frameCount++;
|
frameCount++;
|
||||||
}
|
}
|
||||||
|
|||||||
546
tests/visual/test_full_stack_interactive.cpp.orig
Normal file
546
tests/visual/test_full_stack_interactive.cpp.orig
Normal file
@ -0,0 +1,546 @@
|
|||||||
|
/**
|
||||||
|
* Visual Test: Full Stack Interactive Demo
|
||||||
|
*
|
||||||
|
* Demonstrates complete integration of:
|
||||||
|
* - BgfxRenderer (2D rendering)
|
||||||
|
* - UIModule (widgets)
|
||||||
|
* - InputModule (mouse + keyboard)
|
||||||
|
* - Game logic responding to UI events
|
||||||
|
*
|
||||||
|
* Controls:
|
||||||
|
* - Mouse: Click buttons, drag sliders
|
||||||
|
* - Keyboard: Type in text input, press Space to spawn sprites
|
||||||
|
* - ESC: Exit
|
||||||
|
*
|
||||||
|
* Build modes:
|
||||||
|
* - USE_STATIC_BGFX: Link BgfxRenderer statically (required on Windows)
|
||||||
|
* - Default: Load BgfxRenderer as DLL (works on Linux/Mac)
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <grove/ModuleLoader.h>
|
||||||
|
#include <grove/IntraIOManager.h>
|
||||||
|
#include <grove/IntraIO.h>
|
||||||
|
#include <grove/JsonDataNode.h>
|
||||||
|
#include <SDL.h>
|
||||||
|
#include <SDL_syswm.h>
|
||||||
|
#include <spdlog/spdlog.h>
|
||||||
|
#include <spdlog/sinks/stdout_color_sinks.h>
|
||||||
|
#include <spdlog/sinks/basic_file_sink.h>
|
||||||
|
#include <iostream>
|
||||||
|
#include <vector>
|
||||||
|
#include <random>
|
||||||
|
#include <thread>
|
||||||
|
#include <chrono>
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
|
#include <windows.h>
|
||||||
|
#else
|
||||||
|
#include <dlfcn.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Static linking for BgfxRenderer (required on Windows due to bgfx DLL issues)
|
||||||
|
#ifdef USE_STATIC_BGFX
|
||||||
|
#include "BgfxRendererModule.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Function pointer type for feedEvent (loaded from DLL)
|
||||||
|
typedef void (*FeedEventFunc)(grove::IModule*, const void*);
|
||||||
|
|
||||||
|
using namespace grove;
|
||||||
|
|
||||||
|
// Simple game state
|
||||||
|
struct Sprite {
|
||||||
|
float x, y;
|
||||||
|
float vx, vy;
|
||||||
|
uint32_t color;
|
||||||
|
};
|
||||||
|
|
||||||
|
class GameLogic {
|
||||||
|
public:
|
||||||
|
GameLogic(IIO* io) : m_io(io) {
|
||||||
|
m_logger = spdlog::stdout_color_mt("GameLogic");
|
||||||
|
|
||||||
|
// Subscribe to UI events
|
||||||
|
m_io->subscribe("ui:click");
|
||||||
|
m_io->subscribe("ui:action");
|
||||||
|
m_io->subscribe("ui:value_changed");
|
||||||
|
m_io->subscribe("input:keyboard:key");
|
||||||
|
}
|
||||||
|
|
||||||
|
void update(float deltaTime) {
|
||||||
|
// Update sprites
|
||||||
|
for (auto& sprite : m_sprites) {
|
||||||
|
sprite.x += sprite.vx * deltaTime;
|
||||||
|
sprite.y += sprite.vy * deltaTime;
|
||||||
|
|
||||||
|
// Bounce off walls (1280x720 window)
|
||||||
|
if (sprite.x < 0 || sprite.x > 1280) sprite.vx = -sprite.vx;
|
||||||
|
if (sprite.y < 0 || sprite.y > 720) sprite.vy = -sprite.vy;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Process events
|
||||||
|
while (m_io->hasMessages() > 0) {
|
||||||
|
auto msg = m_io->pullMessage();
|
||||||
|
|
||||||
|
if (msg.topic == "ui:action") {
|
||||||
|
std::string action = msg.data->getString("action", "");
|
||||||
|
m_logger->info("UI Action: {}", action);
|
||||||
|
|
||||||
|
if (action == "spawn_sprite") {
|
||||||
|
spawnSprite();
|
||||||
|
} else if (action == "clear_sprites") {
|
||||||
|
m_sprites.clear();
|
||||||
|
m_logger->info("Cleared all sprites");
|
||||||
|
} else if (action == "toggle_background") {
|
||||||
|
m_darkBackground = !m_darkBackground;
|
||||||
|
m_logger->info("Background: {}", m_darkBackground ? "Dark" : "Light");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (msg.topic == "ui:value_changed") {
|
||||||
|
std::string widgetId = msg.data->getString("widgetId", "");
|
||||||
|
|
||||||
|
if (widgetId == "speed_slider") {
|
||||||
|
m_spawnSpeed = static_cast<float>(msg.data->getDouble("value", 100.0));
|
||||||
|
m_logger->info("Spawn speed: {}", m_spawnSpeed);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (msg.topic == "input:keyboard:key") {
|
||||||
|
int scancode = msg.data->getInt("scancode", 0);
|
||||||
|
bool pressed = msg.data->getBool("pressed", false);
|
||||||
|
|
||||||
|
if (pressed && scancode == SDL_SCANCODE_SPACE) {
|
||||||
|
spawnSprite();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void render(IIO* rendererIO) {
|
||||||
|
// Publish clear color
|
||||||
|
auto clear = std::make_unique<JsonDataNode>("clear");
|
||||||
|
clear->setInt("color", m_darkBackground ? 0x1a1a1aFF : 0x303030FF);
|
||||||
|
rendererIO->publish("render:clear", std::move(clear));
|
||||||
|
|
||||||
|
// Render sprites
|
||||||
|
int layer = 5;
|
||||||
|
for (const auto& sprite : m_sprites) {
|
||||||
|
auto spriteNode = std::make_unique<JsonDataNode>("sprite");
|
||||||
|
spriteNode->setDouble("x", sprite.x);
|
||||||
|
spriteNode->setDouble("y", sprite.y);
|
||||||
|
spriteNode->setDouble("scaleX", 32.0);
|
||||||
|
spriteNode->setDouble("scaleY", 32.0);
|
||||||
|
spriteNode->setDouble("rotation", 0.0);
|
||||||
|
spriteNode->setDouble("u0", 0.0);
|
||||||
|
spriteNode->setDouble("v0", 0.0);
|
||||||
|
spriteNode->setDouble("u1", 1.0);
|
||||||
|
spriteNode->setDouble("v1", 1.0);
|
||||||
|
spriteNode->setInt("color", sprite.color);
|
||||||
|
spriteNode->setInt("textureId", 0); // White texture
|
||||||
|
spriteNode->setInt("layer", layer);
|
||||||
|
rendererIO->publish("render:sprite", std::move(spriteNode));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Render sprite count
|
||||||
|
auto text = std::make_unique<JsonDataNode>("text");
|
||||||
|
text->setDouble("x", 20.0);
|
||||||
|
text->setDouble("y", 20.0);
|
||||||
|
text->setString("text", "Sprites: " + std::to_string(m_sprites.size()) + " (Press SPACE to spawn)");
|
||||||
|
text->setDouble("fontSize", 24.0);
|
||||||
|
text->setInt("color", 0xFFFFFFFF);
|
||||||
|
text->setInt("layer", 2000); // Above UI
|
||||||
|
rendererIO->publish("render:text", std::move(text));
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
void spawnSprite() {
|
||||||
|
std::random_device rd;
|
||||||
|
std::mt19937 gen(rd());
|
||||||
|
std::uniform_real_distribution<float> posX(100.0f, 1820.0f);
|
||||||
|
std::uniform_real_distribution<float> posY(100.0f, 980.0f);
|
||||||
|
std::uniform_real_distribution<float> vel(-1.0f, 1.0f);
|
||||||
|
std::uniform_int_distribution<uint32_t> colorDist(0x80000000, 0xFFFFFFFF);
|
||||||
|
|
||||||
|
Sprite sprite;
|
||||||
|
sprite.x = posX(gen);
|
||||||
|
sprite.y = posY(gen);
|
||||||
|
sprite.vx = vel(gen) * m_spawnSpeed;
|
||||||
|
sprite.vy = vel(gen) * m_spawnSpeed;
|
||||||
|
sprite.color = colorDist(gen) | 0xFF; // Force full alpha
|
||||||
|
|
||||||
|
m_sprites.push_back(sprite);
|
||||||
|
m_logger->info("Spawned sprite at ({}, {})", sprite.x, sprite.y);
|
||||||
|
}
|
||||||
|
|
||||||
|
IIO* m_io;
|
||||||
|
std::shared_ptr<spdlog::logger> m_logger;
|
||||||
|
std::vector<Sprite> m_sprites;
|
||||||
|
float m_spawnSpeed = 100.0f;
|
||||||
|
bool m_darkBackground = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
#undef main // Undefine SDL's main macro for Windows
|
||||||
|
|
||||||
|
int main(int argc, char* argv[]) {
|
||||||
|
// Setup logging to both console AND file
|
||||||
|
try {
|
||||||
|
auto console_sink = std::make_shared<spdlog::sinks::stdout_color_sink_mt>();
|
||||||
|
auto file_sink = std::make_shared<spdlog::sinks::basic_file_sink_mt>("full_stack_demo.log", true);
|
||||||
|
|
||||||
|
std::vector<spdlog::sink_ptr> sinks {console_sink, file_sink};
|
||||||
|
auto logger = std::make_shared<spdlog::logger>("Main", sinks.begin(), sinks.end());
|
||||||
|
spdlog::register_logger(logger);
|
||||||
|
spdlog::set_default_logger(logger);
|
||||||
|
spdlog::set_level(spdlog::level::info);
|
||||||
|
spdlog::flush_on(spdlog::level::info); // Auto-flush pour pas perdre de logs
|
||||||
|
} catch (const std::exception& e) {
|
||||||
|
std::cerr << "Failed to setup logging: " << e.what() << "\n";
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto logger = spdlog::get("Main");
|
||||||
|
|
||||||
|
logger->info("==============================================");
|
||||||
|
logger->info(" Full Stack Interactive Demo");
|
||||||
|
logger->info("==============================================");
|
||||||
|
logger->info("Log file: full_stack_demo.log");
|
||||||
|
|
||||||
|
// Initialize SDL
|
||||||
|
if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_EVENTS) != 0) {
|
||||||
|
logger->error("SDL_Init failed: {}", SDL_GetError());
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create window (windowed mode, not fullscreen)
|
||||||
|
const int WINDOW_WIDTH = 1280;
|
||||||
|
const int WINDOW_HEIGHT = 720;
|
||||||
|
SDL_Window* window = SDL_CreateWindow(
|
||||||
|
"GroveEngine - Full Stack Demo",
|
||||||
|
SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED,
|
||||||
|
WINDOW_WIDTH, WINDOW_HEIGHT,
|
||||||
|
SDL_WINDOW_SHOWN | SDL_WINDOW_RESIZABLE
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!window) {
|
||||||
|
logger->error("SDL_CreateWindow failed: {}", SDL_GetError());
|
||||||
|
SDL_Quit();
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get native window handle
|
||||||
|
SDL_SysWMinfo wmInfo;
|
||||||
|
SDL_VERSION(&wmInfo.version);
|
||||||
|
SDL_GetWindowWMInfo(window, &wmInfo);
|
||||||
|
void* nativeHandle = nullptr;
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
|
nativeHandle = wmInfo.info.win.window;
|
||||||
|
#elif defined(__linux__)
|
||||||
|
nativeHandle = (void*)(uintptr_t)wmInfo.info.x11.window;
|
||||||
|
#elif defined(__APPLE__)
|
||||||
|
nativeHandle = wmInfo.info.cocoa.window;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
logger->info("Native window handle: {}", nativeHandle);
|
||||||
|
|
||||||
|
// Create IIO instances
|
||||||
|
auto& ioManager = IntraIOManager::getInstance();
|
||||||
|
auto rendererIO = ioManager.createInstance("renderer");
|
||||||
|
auto uiIO = ioManager.createInstance("ui");
|
||||||
|
auto inputIO = ioManager.createInstance("input");
|
||||||
|
auto gameIO = ioManager.createInstance("game");
|
||||||
|
|
||||||
|
// Load modules
|
||||||
|
ModuleLoader uiLoader, inputLoader;
|
||||||
|
#ifndef USE_STATIC_BGFX
|
||||||
|
ModuleLoader rendererLoader;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
std::string uiPath = "./modules/UIModule.dll";
|
||||||
|
std::string inputPath = "./modules/InputModule.dll";
|
||||||
|
|
||||||
|
#ifndef _WIN32
|
||||||
|
uiPath = "./modules/libUIModule.so";
|
||||||
|
inputPath = "./modules/libInputModule.so";
|
||||||
|
#endif
|
||||||
|
|
||||||
|
logger->info("Loading modules...");
|
||||||
|
|
||||||
|
// Load/Create BgfxRenderer
|
||||||
|
std::unique_ptr<IModule> renderer;
|
||||||
|
#ifdef USE_STATIC_BGFX
|
||||||
|
// Static linking: instantiate directly (required on Windows)
|
||||||
|
renderer = std::make_unique<BgfxRendererModule>();
|
||||||
|
logger->info("✅ BgfxRenderer created (static)");
|
||||||
|
#else
|
||||||
|
// Dynamic linking: load from DLL
|
||||||
|
std::string rendererPath = "./modules/BgfxRenderer.dll";
|
||||||
|
#ifndef _WIN32
|
||||||
|
rendererPath = "./modules/libBgfxRenderer.so";
|
||||||
|
#endif
|
||||||
|
try {
|
||||||
|
renderer = rendererLoader.load(rendererPath, "renderer");
|
||||||
|
logger->info("✅ BgfxRenderer loaded (dynamic)");
|
||||||
|
} catch (const std::exception& e) {
|
||||||
|
logger->error("Failed to load BgfxRenderer: {}", e.what());
|
||||||
|
SDL_DestroyWindow(window);
|
||||||
|
SDL_Quit();
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Configure BgfxRenderer
|
||||||
|
JsonDataNode rendererConfig("config");
|
||||||
|
rendererConfig.setInt("windowWidth", WINDOW_WIDTH);
|
||||||
|
rendererConfig.setInt("windowHeight", WINDOW_HEIGHT);
|
||||||
|
rendererConfig.setString("backend", "opengl"); // Force OpenGL instead of D3D11
|
||||||
|
rendererConfig.setBool("vsync", true);
|
||||||
|
rendererConfig.setInt("nativeWindowHandle", (int)(intptr_t)nativeHandle);
|
||||||
|
renderer->setConfiguration(rendererConfig, rendererIO.get(), nullptr);
|
||||||
|
|
||||||
|
// Load UIModule
|
||||||
|
std::unique_ptr<IModule> uiModule;
|
||||||
|
try {
|
||||||
|
uiModule = uiLoader.load(uiPath, "ui");
|
||||||
|
logger->info("✅ UIModule loaded");
|
||||||
|
} catch (const std::exception& e) {
|
||||||
|
logger->error("Failed to load UIModule: {}", e.what());
|
||||||
|
renderer->shutdown();
|
||||||
|
SDL_DestroyWindow(window);
|
||||||
|
SDL_Quit();
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Configure UIModule with inline layout
|
||||||
|
JsonDataNode uiConfig("config");
|
||||||
|
uiConfig.setInt("windowWidth", WINDOW_WIDTH);
|
||||||
|
uiConfig.setInt("windowHeight", WINDOW_HEIGHT);
|
||||||
|
uiConfig.setInt("baseLayer", 1000);
|
||||||
|
|
||||||
|
// Create inline layout
|
||||||
|
auto layout = std::make_unique<JsonDataNode>("layout");
|
||||||
|
auto widgets = std::make_unique<JsonDataNode>("widgets");
|
||||||
|
|
||||||
|
// Panel background
|
||||||
|
auto panel = std::make_unique<JsonDataNode>("panel");
|
||||||
|
panel->setString("type", "UIPanel");
|
||||||
|
panel->setString("id", "control_panel");
|
||||||
|
panel->setInt("x", 20);
|
||||||
|
panel->setInt("y", 80);
|
||||||
|
panel->setInt("width", 300);
|
||||||
|
panel->setInt("height", 300);
|
||||||
|
panel->setInt("color", 0x404040CC); // Semi-transparent gray
|
||||||
|
widgets->setChild("panel", std::move(panel));
|
||||||
|
|
||||||
|
// Title label
|
||||||
|
auto title = std::make_unique<JsonDataNode>("title");
|
||||||
|
title->setString("type", "UILabel");
|
||||||
|
title->setString("id", "title_label");
|
||||||
|
title->setInt("x", 40);
|
||||||
|
title->setInt("y", 100);
|
||||||
|
title->setInt("width", 260);
|
||||||
|
title->setInt("height", 40);
|
||||||
|
title->setString("text", "Control Panel");
|
||||||
|
title->setInt("fontSize", 28);
|
||||||
|
title->setInt("color", 0xFFFFFFFF);
|
||||||
|
widgets->setChild("title", std::move(title));
|
||||||
|
|
||||||
|
// Spawn button
|
||||||
|
auto spawnBtn = std::make_unique<JsonDataNode>("spawn_button");
|
||||||
|
spawnBtn->setString("type", "UIButton");
|
||||||
|
spawnBtn->setString("id", "spawn_button");
|
||||||
|
spawnBtn->setInt("x", 40);
|
||||||
|
spawnBtn->setInt("y", 160);
|
||||||
|
spawnBtn->setInt("width", 120);
|
||||||
|
spawnBtn->setInt("height", 40);
|
||||||
|
spawnBtn->setString("text", "Spawn");
|
||||||
|
spawnBtn->setString("action", "spawn_sprite");
|
||||||
|
spawnBtn->setInt("fontSize", 20);
|
||||||
|
widgets->setChild("spawn_button", std::move(spawnBtn));
|
||||||
|
|
||||||
|
// Clear button
|
||||||
|
auto clearBtn = std::make_unique<JsonDataNode>("clear_button");
|
||||||
|
clearBtn->setString("type", "UIButton");
|
||||||
|
clearBtn->setString("id", "clear_button");
|
||||||
|
clearBtn->setInt("x", 180);
|
||||||
|
clearBtn->setInt("y", 160);
|
||||||
|
clearBtn->setInt("width", 120);
|
||||||
|
clearBtn->setInt("height", 40);
|
||||||
|
clearBtn->setString("text", "Clear");
|
||||||
|
clearBtn->setString("action", "clear_sprites");
|
||||||
|
clearBtn->setInt("fontSize", 20);
|
||||||
|
widgets->setChild("clear_button", std::move(clearBtn));
|
||||||
|
|
||||||
|
// Speed slider
|
||||||
|
auto slider = std::make_unique<JsonDataNode>("speed_slider");
|
||||||
|
slider->setString("type", "UISlider");
|
||||||
|
slider->setString("id", "speed_slider");
|
||||||
|
slider->setInt("x", 40);
|
||||||
|
slider->setInt("y", 220);
|
||||||
|
slider->setInt("width", 260);
|
||||||
|
slider->setInt("height", 30);
|
||||||
|
slider->setDouble("min", 10.0);
|
||||||
|
slider->setDouble("max", 500.0);
|
||||||
|
slider->setDouble("value", 100.0);
|
||||||
|
slider->setString("orientation", "horizontal");
|
||||||
|
widgets->setChild("speed_slider", std::move(slider));
|
||||||
|
|
||||||
|
// Speed label
|
||||||
|
auto speedLabel = std::make_unique<JsonDataNode>("speed_label");
|
||||||
|
speedLabel->setString("type", "UILabel");
|
||||||
|
speedLabel->setString("id", "speed_label");
|
||||||
|
speedLabel->setInt("x", 40);
|
||||||
|
speedLabel->setInt("y", 260);
|
||||||
|
speedLabel->setInt("width", 260);
|
||||||
|
speedLabel->setInt("height", 30);
|
||||||
|
speedLabel->setString("text", "Speed: 100");
|
||||||
|
speedLabel->setInt("fontSize", 18);
|
||||||
|
speedLabel->setInt("color", 0xCCCCCCFF);
|
||||||
|
widgets->setChild("speed_label", std::move(speedLabel));
|
||||||
|
|
||||||
|
// Background toggle button
|
||||||
|
auto bgBtn = std::make_unique<JsonDataNode>("bg_button");
|
||||||
|
bgBtn->setString("type", "UIButton");
|
||||||
|
bgBtn->setString("id", "bg_button");
|
||||||
|
bgBtn->setInt("x", 40);
|
||||||
|
bgBtn->setInt("y", 310);
|
||||||
|
bgBtn->setInt("width", 260);
|
||||||
|
bgBtn->setInt("height", 40);
|
||||||
|
bgBtn->setString("text", "Toggle Background");
|
||||||
|
bgBtn->setString("action", "toggle_background");
|
||||||
|
bgBtn->setInt("fontSize", 18);
|
||||||
|
widgets->setChild("bg_button", std::move(bgBtn));
|
||||||
|
|
||||||
|
layout->setChild("widgets", std::move(widgets));
|
||||||
|
uiConfig.setChild("layout", std::move(layout));
|
||||||
|
|
||||||
|
uiModule->setConfiguration(uiConfig, uiIO.get(), nullptr);
|
||||||
|
|
||||||
|
// Load InputModule
|
||||||
|
std::unique_ptr<IModule> inputModuleBase;
|
||||||
|
FeedEventFunc feedEventFunc = nullptr;
|
||||||
|
try {
|
||||||
|
inputModuleBase = inputLoader.load(inputPath, "input");
|
||||||
|
logger->info("✅ InputModule loaded");
|
||||||
|
|
||||||
|
// Get the feedEvent function from the DLL
|
||||||
|
#ifdef _WIN32
|
||||||
|
HMODULE inputDll = LoadLibraryA(inputPath.c_str());
|
||||||
|
if (inputDll) {
|
||||||
|
feedEventFunc = (FeedEventFunc)GetProcAddress(inputDll, "feedEventToInputModule");
|
||||||
|
if (!feedEventFunc) {
|
||||||
|
logger->warn("feedEventToInputModule not found in InputModule.dll");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
void* inputDll = dlopen(inputPath.c_str(), RTLD_NOW);
|
||||||
|
if (inputDll) {
|
||||||
|
feedEventFunc = (FeedEventFunc)dlsym(inputDll, "feedEventToInputModule");
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
} catch (const std::exception& e) {
|
||||||
|
logger->error("Failed to load InputModule: {}", e.what());
|
||||||
|
uiModule->shutdown();
|
||||||
|
renderer->shutdown();
|
||||||
|
SDL_DestroyWindow(window);
|
||||||
|
SDL_Quit();
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!feedEventFunc) {
|
||||||
|
logger->error("Failed to get feedEventToInputModule function");
|
||||||
|
uiModule->shutdown();
|
||||||
|
renderer->shutdown();
|
||||||
|
SDL_DestroyWindow(window);
|
||||||
|
SDL_Quit();
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Configure InputModule
|
||||||
|
JsonDataNode inputConfig("config");
|
||||||
|
inputConfig.setString("backend", "sdl");
|
||||||
|
inputConfig.setBool("enableMouse", true);
|
||||||
|
inputConfig.setBool("enableKeyboard", true);
|
||||||
|
inputModuleBase->setConfiguration(inputConfig, inputIO.get(), nullptr);
|
||||||
|
|
||||||
|
// Create game logic
|
||||||
|
GameLogic gameLogic(gameIO.get());
|
||||||
|
|
||||||
|
logger->info("\n==============================================");
|
||||||
|
logger->info("Demo started! Controls:");
|
||||||
|
logger->info(" - Click buttons to spawn/clear sprites");
|
||||||
|
logger->info(" - Drag slider to change speed");
|
||||||
|
logger->info(" - Press SPACE to spawn sprite");
|
||||||
|
logger->info(" - Press ESC to exit");
|
||||||
|
logger->info("==============================================\n");
|
||||||
|
|
||||||
|
// Main loop
|
||||||
|
bool running = true;
|
||||||
|
Uint64 lastTime = SDL_GetPerformanceCounter();
|
||||||
|
int frameCount = 0;
|
||||||
|
|
||||||
|
logger->info("Entering main loop...");
|
||||||
|
spdlog::default_logger()->flush();
|
||||||
|
|
||||||
|
while (running) {
|
||||||
|
logger->info("Frame {} start", frameCount);
|
||||||
|
spdlog::default_logger()->flush();
|
||||||
|
// Handle SDL events
|
||||||
|
SDL_Event event;
|
||||||
|
while (SDL_PollEvent(&event)) {
|
||||||
|
if (event.type == SDL_QUIT) {
|
||||||
|
running = false;
|
||||||
|
}
|
||||||
|
else if (event.type == SDL_KEYDOWN && event.key.keysym.scancode == SDL_SCANCODE_ESCAPE) {
|
||||||
|
running = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Feed to InputModule via exported C function
|
||||||
|
feedEventFunc(inputModuleBase.get(), &event);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate deltaTime
|
||||||
|
Uint64 now = SDL_GetPerformanceCounter();
|
||||||
|
double deltaTime = (now - lastTime) / (double)SDL_GetPerformanceFrequency();
|
||||||
|
lastTime = now;
|
||||||
|
|
||||||
|
// Clamp deltaTime to avoid huge jumps
|
||||||
|
if (deltaTime > 0.1) deltaTime = 0.016;
|
||||||
|
|
||||||
|
// Process modules
|
||||||
|
JsonDataNode input("input");
|
||||||
|
input.setDouble("deltaTime", deltaTime);
|
||||||
|
input.setInt("frameCount", frameCount);
|
||||||
|
|
||||||
|
logger->info("Processing input module...");
|
||||||
|
inputModuleBase->process(input);
|
||||||
|
logger->info("Processing UI module...");
|
||||||
|
uiModule->process(input);
|
||||||
|
logger->info("Updating game logic...");
|
||||||
|
gameLogic.update((float)deltaTime);
|
||||||
|
logger->info("Rendering game...");
|
||||||
|
gameLogic.render(rendererIO.get());
|
||||||
|
logger->info("Processing renderer...");
|
||||||
|
spdlog::default_logger()->flush();
|
||||||
|
renderer->process(input);
|
||||||
|
logger->info("Frame {} complete", frameCount);
|
||||||
|
|
||||||
|
frameCount++;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cleanup
|
||||||
|
logger->info("\nShutting down...");
|
||||||
|
inputModuleBase->shutdown();
|
||||||
|
uiModule->shutdown();
|
||||||
|
renderer->shutdown();
|
||||||
|
|
||||||
|
ioManager.removeInstance("renderer");
|
||||||
|
ioManager.removeInstance("ui");
|
||||||
|
ioManager.removeInstance("input");
|
||||||
|
ioManager.removeInstance("game");
|
||||||
|
|
||||||
|
SDL_DestroyWindow(window);
|
||||||
|
SDL_Quit();
|
||||||
|
|
||||||
|
logger->info("✅ Demo exited cleanly");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
660
tests/visual/test_renderer_showcase.cpp
Normal file
660
tests/visual/test_renderer_showcase.cpp
Normal file
@ -0,0 +1,660 @@
|
|||||||
|
/**
|
||||||
|
* BgfxRenderer Complete Showcase
|
||||||
|
*
|
||||||
|
* Demonstrates ALL rendering features of BgfxRendererModule:
|
||||||
|
* - Sprites (static, animated, colored, layered, rotated, scaled)
|
||||||
|
* - Text (different sizes, colors, multi-line)
|
||||||
|
* - Particles (with life, velocity, size, additive blending)
|
||||||
|
* - Tilemap (simple grid with tile indices)
|
||||||
|
* - Debug primitives (lines, rectangles wireframe and filled)
|
||||||
|
* - Camera (orthographic projection, panning, zooming)
|
||||||
|
* - Clear color
|
||||||
|
*
|
||||||
|
* Controls:
|
||||||
|
* - Arrow keys: Move camera
|
||||||
|
* - +/-: Zoom in/out
|
||||||
|
* - SPACE: Spawn explosion particles
|
||||||
|
* - C: Cycle clear color
|
||||||
|
* - ESC: Exit
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <SDL.h>
|
||||||
|
#include <SDL_syswm.h>
|
||||||
|
#include <iostream>
|
||||||
|
#include <cmath>
|
||||||
|
#include <memory>
|
||||||
|
#include <vector>
|
||||||
|
#include <random>
|
||||||
|
|
||||||
|
#include "BgfxRendererModule.h"
|
||||||
|
#include <grove/JsonDataNode.h>
|
||||||
|
#include <grove/IntraIOManager.h>
|
||||||
|
#include <grove/IntraIO.h>
|
||||||
|
#include <spdlog/spdlog.h>
|
||||||
|
#include <spdlog/sinks/stdout_color_sinks.h>
|
||||||
|
|
||||||
|
using namespace grove;
|
||||||
|
|
||||||
|
// Particle structure for CPU-side simulation
|
||||||
|
struct Particle {
|
||||||
|
float x, y;
|
||||||
|
float vx, vy;
|
||||||
|
float size;
|
||||||
|
float life;
|
||||||
|
float maxLife;
|
||||||
|
uint32_t color;
|
||||||
|
bool alive;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Simple tilemap data (10x8 grid)
|
||||||
|
static const int TILEMAP_WIDTH = 10;
|
||||||
|
static const int TILEMAP_HEIGHT = 8;
|
||||||
|
static const int TILE_SIZE = 32;
|
||||||
|
static uint16_t tilemapData[TILEMAP_WIDTH * TILEMAP_HEIGHT] = {
|
||||||
|
1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
|
||||||
|
1, 0, 0, 0, 0, 0, 0, 0, 0, 1,
|
||||||
|
1, 0, 2, 2, 0, 0, 3, 3, 0, 1,
|
||||||
|
1, 0, 2, 2, 0, 0, 3, 3, 0, 1,
|
||||||
|
1, 0, 0, 0, 0, 0, 0, 0, 0, 1,
|
||||||
|
1, 0, 4, 4, 4, 4, 4, 4, 0, 1,
|
||||||
|
1, 0, 0, 0, 0, 0, 0, 0, 0, 1,
|
||||||
|
1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Clear colors to cycle through
|
||||||
|
static const uint32_t clearColors[] = {
|
||||||
|
0x1a1a2eFF, // Dark blue-gray
|
||||||
|
0x16213eFF, // Navy blue
|
||||||
|
0x0f3460FF, // Deep blue
|
||||||
|
0x1e5128FF, // Forest green
|
||||||
|
0x2c3333FF, // Dark teal
|
||||||
|
0x3d0000FF, // Dark red
|
||||||
|
};
|
||||||
|
static const int numClearColors = sizeof(clearColors) / sizeof(clearColors[0]);
|
||||||
|
|
||||||
|
class RendererShowcase {
|
||||||
|
public:
|
||||||
|
RendererShowcase()
|
||||||
|
: m_rng(std::random_device{}())
|
||||||
|
{
|
||||||
|
m_particles.reserve(500);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool init(SDL_Window* window) {
|
||||||
|
// Get native window handle
|
||||||
|
SDL_SysWMinfo wmi;
|
||||||
|
SDL_VERSION(&wmi.version);
|
||||||
|
SDL_GetWindowWMInfo(window, &wmi);
|
||||||
|
|
||||||
|
m_logger = spdlog::stdout_color_mt("Showcase");
|
||||||
|
spdlog::set_level(spdlog::level::info);
|
||||||
|
|
||||||
|
m_logger->info("=== BgfxRenderer Complete Showcase ===");
|
||||||
|
|
||||||
|
// Create IIO instances - IMPORTANT: separate for publisher and subscriber
|
||||||
|
// Keep shared_ptr alive, use IIO* abstract interface
|
||||||
|
m_rendererIOPtr = IntraIOManager::getInstance().createInstance("renderer");
|
||||||
|
m_gameIOPtr = IntraIOManager::getInstance().createInstance("game");
|
||||||
|
m_rendererIO = m_rendererIOPtr.get();
|
||||||
|
m_gameIO = m_gameIOPtr.get();
|
||||||
|
|
||||||
|
// Create and configure renderer
|
||||||
|
m_renderer = std::make_unique<BgfxRendererModule>();
|
||||||
|
|
||||||
|
JsonDataNode config("config");
|
||||||
|
config.setDouble("nativeWindowHandle", static_cast<double>(reinterpret_cast<uintptr_t>(wmi.info.win.window)));
|
||||||
|
config.setInt("windowWidth", 1024);
|
||||||
|
config.setInt("windowHeight", 768);
|
||||||
|
config.setString("backend", "d3d11");
|
||||||
|
config.setBool("vsync", true);
|
||||||
|
|
||||||
|
// Load textures - try multiple paths for flexibility
|
||||||
|
// Works from build/ or build/tests/
|
||||||
|
config.setString("texture1", "../assets/textures/1f440.png"); // Eye emoji
|
||||||
|
config.setString("texture2", "../assets/textures/5oxaxt1vo2f91.jpg"); // Image
|
||||||
|
|
||||||
|
m_renderer->setConfiguration(config, m_rendererIO, nullptr);
|
||||||
|
|
||||||
|
m_logger->info("Renderer initialized");
|
||||||
|
m_logger->info("Controls:");
|
||||||
|
m_logger->info(" Arrows: Move camera");
|
||||||
|
m_logger->info(" +/-: Zoom");
|
||||||
|
m_logger->info(" SPACE: Spawn particles");
|
||||||
|
m_logger->info(" C: Cycle clear color");
|
||||||
|
m_logger->info(" ESC: Exit");
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void handleInput(SDL_Event& e) {
|
||||||
|
if (e.type == SDL_KEYDOWN) {
|
||||||
|
switch (e.key.keysym.sym) {
|
||||||
|
case SDLK_LEFT: m_cameraVX = -200.0f; break;
|
||||||
|
case SDLK_RIGHT: m_cameraVX = 200.0f; break;
|
||||||
|
case SDLK_UP: m_cameraVY = -200.0f; break;
|
||||||
|
case SDLK_DOWN: m_cameraVY = 200.0f; break;
|
||||||
|
case SDLK_PLUS:
|
||||||
|
case SDLK_EQUALS:
|
||||||
|
m_cameraZoom = std::min(4.0f, m_cameraZoom * 1.1f);
|
||||||
|
break;
|
||||||
|
case SDLK_MINUS:
|
||||||
|
m_cameraZoom = std::max(0.25f, m_cameraZoom / 1.1f);
|
||||||
|
break;
|
||||||
|
case SDLK_SPACE:
|
||||||
|
spawnExplosion(512.0f + m_cameraX, 400.0f + m_cameraY);
|
||||||
|
break;
|
||||||
|
case SDLK_c:
|
||||||
|
m_clearColorIndex = (m_clearColorIndex + 1) % numClearColors;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (e.type == SDL_KEYUP) {
|
||||||
|
switch (e.key.keysym.sym) {
|
||||||
|
case SDLK_LEFT:
|
||||||
|
case SDLK_RIGHT:
|
||||||
|
m_cameraVX = 0.0f;
|
||||||
|
break;
|
||||||
|
case SDLK_UP:
|
||||||
|
case SDLK_DOWN:
|
||||||
|
m_cameraVY = 0.0f;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void update(float dt) {
|
||||||
|
m_time += dt;
|
||||||
|
m_frameCount++;
|
||||||
|
|
||||||
|
// Update camera position
|
||||||
|
m_cameraX += m_cameraVX * dt;
|
||||||
|
m_cameraY += m_cameraVY * dt;
|
||||||
|
|
||||||
|
// Update particles
|
||||||
|
updateParticles(dt);
|
||||||
|
}
|
||||||
|
|
||||||
|
void render() {
|
||||||
|
// 1. Set clear color
|
||||||
|
sendClearColor();
|
||||||
|
|
||||||
|
// 2. Set camera
|
||||||
|
sendCamera();
|
||||||
|
|
||||||
|
// 3. Render tilemap (background layer)
|
||||||
|
sendTilemap();
|
||||||
|
|
||||||
|
// 4. Render sprites (multiple layers)
|
||||||
|
sendSprites();
|
||||||
|
|
||||||
|
// 5. Render particles
|
||||||
|
sendParticles();
|
||||||
|
|
||||||
|
// 6. Render text
|
||||||
|
sendText();
|
||||||
|
|
||||||
|
// 7. Render debug primitives
|
||||||
|
sendDebugPrimitives();
|
||||||
|
|
||||||
|
// Process frame
|
||||||
|
JsonDataNode input("input");
|
||||||
|
input.setDouble("deltaTime", 0.016);
|
||||||
|
input.setInt("frameCount", m_frameCount);
|
||||||
|
m_renderer->process(input);
|
||||||
|
}
|
||||||
|
|
||||||
|
void shutdown() {
|
||||||
|
m_renderer->shutdown();
|
||||||
|
IntraIOManager::getInstance().removeInstance("renderer");
|
||||||
|
IntraIOManager::getInstance().removeInstance("game");
|
||||||
|
m_logger->info("Showcase shutdown complete");
|
||||||
|
}
|
||||||
|
|
||||||
|
int getFrameCount() const { return m_frameCount; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
void sendClearColor() {
|
||||||
|
auto clear = std::make_unique<JsonDataNode>("clear");
|
||||||
|
clear->setInt("color", clearColors[m_clearColorIndex]);
|
||||||
|
m_gameIO->publish("render:clear", std::move(clear));
|
||||||
|
}
|
||||||
|
|
||||||
|
void sendCamera() {
|
||||||
|
auto cam = std::make_unique<JsonDataNode>("camera");
|
||||||
|
cam->setDouble("x", m_cameraX);
|
||||||
|
cam->setDouble("y", m_cameraY);
|
||||||
|
cam->setDouble("zoom", m_cameraZoom);
|
||||||
|
cam->setInt("viewportX", 0);
|
||||||
|
cam->setInt("viewportY", 0);
|
||||||
|
cam->setInt("viewportW", 1024);
|
||||||
|
cam->setInt("viewportH", 768);
|
||||||
|
m_gameIO->publish("render:camera", std::move(cam));
|
||||||
|
}
|
||||||
|
|
||||||
|
void sendTilemap() {
|
||||||
|
auto tilemap = std::make_unique<JsonDataNode>("tilemap");
|
||||||
|
tilemap->setDouble("x", 50.0);
|
||||||
|
tilemap->setDouble("y", 400.0);
|
||||||
|
tilemap->setInt("width", TILEMAP_WIDTH);
|
||||||
|
tilemap->setInt("height", TILEMAP_HEIGHT);
|
||||||
|
tilemap->setInt("tileW", TILE_SIZE);
|
||||||
|
tilemap->setInt("tileH", TILE_SIZE);
|
||||||
|
tilemap->setInt("textureId", 0);
|
||||||
|
|
||||||
|
// Convert tilemap to comma-separated string
|
||||||
|
std::string tileData;
|
||||||
|
for (int i = 0; i < TILEMAP_WIDTH * TILEMAP_HEIGHT; ++i) {
|
||||||
|
if (i > 0) tileData += ",";
|
||||||
|
tileData += std::to_string(tilemapData[i]);
|
||||||
|
}
|
||||||
|
tilemap->setString("tileData", tileData);
|
||||||
|
|
||||||
|
m_gameIO->publish("render:tilemap", std::move(tilemap));
|
||||||
|
}
|
||||||
|
|
||||||
|
void sendSprites() {
|
||||||
|
// Layer 0: Background sprites with TEXTURE 2 (image)
|
||||||
|
for (int i = 0; i < 5; ++i) {
|
||||||
|
auto sprite = std::make_unique<JsonDataNode>("sprite");
|
||||||
|
sprite->setDouble("x", 100 + i * 150);
|
||||||
|
sprite->setDouble("y", 50);
|
||||||
|
sprite->setDouble("scaleX", 120.0);
|
||||||
|
sprite->setDouble("scaleY", 90.0);
|
||||||
|
sprite->setDouble("rotation", 0.0);
|
||||||
|
sprite->setInt("color", 0xFFFFFFFF); // White (no tint)
|
||||||
|
sprite->setInt("layer", 0);
|
||||||
|
sprite->setInt("textureId", 2); // Image texture
|
||||||
|
m_gameIO->publish("render:sprite", std::move(sprite));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Layer 5: Bouncing EYE EMOJIS with texture 1
|
||||||
|
for (int i = 0; i < 5; ++i) {
|
||||||
|
float offset = std::sin(m_time * 2.0f + i * 1.2f) * 40.0f;
|
||||||
|
auto sprite = std::make_unique<JsonDataNode>("sprite");
|
||||||
|
sprite->setDouble("x", 100 + i * 180);
|
||||||
|
sprite->setDouble("y", 200 + offset);
|
||||||
|
sprite->setDouble("scaleX", 64.0);
|
||||||
|
sprite->setDouble("scaleY", 64.0);
|
||||||
|
sprite->setDouble("rotation", 0.0);
|
||||||
|
sprite->setInt("color", 0xFFFFFFFF); // No tint
|
||||||
|
sprite->setInt("layer", 5);
|
||||||
|
sprite->setInt("textureId", 1); // Eye emoji
|
||||||
|
m_gameIO->publish("render:sprite", std::move(sprite));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Layer 10: Rotating eye emoji
|
||||||
|
{
|
||||||
|
auto sprite = std::make_unique<JsonDataNode>("sprite");
|
||||||
|
sprite->setDouble("x", 700);
|
||||||
|
sprite->setDouble("y", 200);
|
||||||
|
sprite->setDouble("scaleX", 100.0);
|
||||||
|
sprite->setDouble("scaleY", 100.0);
|
||||||
|
sprite->setDouble("rotation", m_time); // Radians
|
||||||
|
sprite->setInt("color", 0xFFFFFFFF);
|
||||||
|
sprite->setInt("layer", 10);
|
||||||
|
sprite->setInt("textureId", 1); // Eye emoji
|
||||||
|
m_gameIO->publish("render:sprite", std::move(sprite));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Layer 15: Scaling/pulsing image
|
||||||
|
{
|
||||||
|
float scale = 80.0f + std::sin(m_time * 3.0f) * 30.0f;
|
||||||
|
auto sprite = std::make_unique<JsonDataNode>("sprite");
|
||||||
|
sprite->setDouble("x", 850);
|
||||||
|
sprite->setDouble("y", 200);
|
||||||
|
sprite->setDouble("scaleX", scale);
|
||||||
|
sprite->setDouble("scaleY", scale * 0.75f);
|
||||||
|
sprite->setDouble("rotation", 0.0);
|
||||||
|
sprite->setInt("color", 0xFFFFFFFF);
|
||||||
|
sprite->setInt("layer", 15);
|
||||||
|
sprite->setInt("textureId", 2); // Image
|
||||||
|
m_gameIO->publish("render:sprite", std::move(sprite));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Layer 20: Tinted sprites (color overlay on texture)
|
||||||
|
{
|
||||||
|
uint32_t colors[] = { 0xFF8888FF, 0x88FF88FF, 0x8888FFFF, 0xFFFF88FF };
|
||||||
|
for (int i = 0; i < 4; ++i) {
|
||||||
|
auto sprite = std::make_unique<JsonDataNode>("sprite");
|
||||||
|
sprite->setDouble("x", 100 + i * 100);
|
||||||
|
sprite->setDouble("y", 320);
|
||||||
|
sprite->setDouble("scaleX", 80.0);
|
||||||
|
sprite->setDouble("scaleY", 80.0);
|
||||||
|
sprite->setDouble("rotation", 0.0);
|
||||||
|
sprite->setInt("color", colors[i]); // Tinted
|
||||||
|
sprite->setInt("layer", 20);
|
||||||
|
sprite->setInt("textureId", 1); // Eye emoji with color tint
|
||||||
|
m_gameIO->publish("render:sprite", std::move(sprite));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Layer 25: Grid of small images
|
||||||
|
for (int row = 0; row < 2; ++row) {
|
||||||
|
for (int col = 0; col < 4; ++col) {
|
||||||
|
auto sprite = std::make_unique<JsonDataNode>("sprite");
|
||||||
|
sprite->setDouble("x", 550 + col * 70);
|
||||||
|
sprite->setDouble("y", 320 + row * 55);
|
||||||
|
sprite->setDouble("scaleX", 60.0);
|
||||||
|
sprite->setDouble("scaleY", 45.0);
|
||||||
|
sprite->setDouble("rotation", 0.0);
|
||||||
|
sprite->setInt("color", 0xFFFFFFFF);
|
||||||
|
sprite->setInt("layer", 25);
|
||||||
|
sprite->setInt("textureId", 2); // Image
|
||||||
|
m_gameIO->publish("render:sprite", std::move(sprite));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void sendParticles() {
|
||||||
|
for (const auto& p : m_particles) {
|
||||||
|
if (!p.alive) continue;
|
||||||
|
|
||||||
|
auto particle = std::make_unique<JsonDataNode>("particle");
|
||||||
|
particle->setDouble("x", p.x);
|
||||||
|
particle->setDouble("y", p.y);
|
||||||
|
particle->setDouble("vx", p.vx);
|
||||||
|
particle->setDouble("vy", p.vy);
|
||||||
|
particle->setDouble("size", p.size);
|
||||||
|
particle->setDouble("life", p.life / p.maxLife); // Normalized 0-1
|
||||||
|
particle->setInt("color", p.color);
|
||||||
|
particle->setInt("textureId", 0);
|
||||||
|
m_gameIO->publish("render:particle", std::move(particle));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void sendText() {
|
||||||
|
// Title (large)
|
||||||
|
{
|
||||||
|
auto text = std::make_unique<JsonDataNode>("text");
|
||||||
|
text->setDouble("x", 10);
|
||||||
|
text->setDouble("y", 10);
|
||||||
|
text->setString("text", "BgfxRenderer Showcase");
|
||||||
|
text->setInt("fontSize", 32);
|
||||||
|
text->setInt("color", 0xFFFFFFFF);
|
||||||
|
text->setInt("layer", 100);
|
||||||
|
m_gameIO->publish("render:text", std::move(text));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Info text
|
||||||
|
{
|
||||||
|
auto text = std::make_unique<JsonDataNode>("text");
|
||||||
|
text->setDouble("x", 10);
|
||||||
|
text->setDouble("y", 50);
|
||||||
|
char buf[128];
|
||||||
|
snprintf(buf, sizeof(buf), "Frame: %d Camera: (%.0f, %.0f) Zoom: %.2fx",
|
||||||
|
m_frameCount, m_cameraX, m_cameraY, m_cameraZoom);
|
||||||
|
text->setString("text", buf);
|
||||||
|
text->setInt("fontSize", 16);
|
||||||
|
text->setInt("color", 0xAAAAAAFF);
|
||||||
|
text->setInt("layer", 100);
|
||||||
|
m_gameIO->publish("render:text", std::move(text));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Particles count
|
||||||
|
{
|
||||||
|
auto text = std::make_unique<JsonDataNode>("text");
|
||||||
|
text->setDouble("x", 10);
|
||||||
|
text->setDouble("y", 70);
|
||||||
|
char buf[64];
|
||||||
|
int aliveCount = 0;
|
||||||
|
for (const auto& p : m_particles) if (p.alive) aliveCount++;
|
||||||
|
snprintf(buf, sizeof(buf), "Particles: %d", aliveCount);
|
||||||
|
text->setString("text", buf);
|
||||||
|
text->setInt("fontSize", 16);
|
||||||
|
text->setInt("color", 0xFFCC00FF);
|
||||||
|
text->setInt("layer", 100);
|
||||||
|
m_gameIO->publish("render:text", std::move(text));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Controls help
|
||||||
|
{
|
||||||
|
auto text = std::make_unique<JsonDataNode>("text");
|
||||||
|
text->setDouble("x", 750);
|
||||||
|
text->setDouble("y", 730);
|
||||||
|
text->setString("text", "SPACE: Particles | C: Color | Arrows: Pan | +/-: Zoom");
|
||||||
|
text->setInt("fontSize", 14);
|
||||||
|
text->setInt("color", 0x888888FF);
|
||||||
|
text->setInt("layer", 100);
|
||||||
|
m_gameIO->publish("render:text", std::move(text));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Multi-line text demo
|
||||||
|
{
|
||||||
|
auto text = std::make_unique<JsonDataNode>("text");
|
||||||
|
text->setDouble("x", 700);
|
||||||
|
text->setDouble("y", 300);
|
||||||
|
text->setString("text", "Multi-line\nText Demo\nWith Colors!");
|
||||||
|
text->setInt("fontSize", 20);
|
||||||
|
text->setInt("color", 0x00FF88FF);
|
||||||
|
text->setInt("layer", 100);
|
||||||
|
m_gameIO->publish("render:text", std::move(text));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void sendDebugPrimitives() {
|
||||||
|
// Grid lines
|
||||||
|
for (int i = 0; i <= 10; ++i) {
|
||||||
|
// Vertical lines
|
||||||
|
auto vline = std::make_unique<JsonDataNode>("line");
|
||||||
|
vline->setDouble("x1", 50 + i * 32);
|
||||||
|
vline->setDouble("y1", 400);
|
||||||
|
vline->setDouble("x2", 50 + i * 32);
|
||||||
|
vline->setDouble("y2", 400 + TILEMAP_HEIGHT * 32);
|
||||||
|
vline->setInt("color", 0x444444FF);
|
||||||
|
m_gameIO->publish("render:debug:line", std::move(vline));
|
||||||
|
}
|
||||||
|
for (int i = 0; i <= 8; ++i) {
|
||||||
|
// Horizontal lines
|
||||||
|
auto hline = std::make_unique<JsonDataNode>("line");
|
||||||
|
hline->setDouble("x1", 50);
|
||||||
|
hline->setDouble("y1", 400 + i * 32);
|
||||||
|
hline->setDouble("x2", 50 + TILEMAP_WIDTH * 32);
|
||||||
|
hline->setDouble("y2", 400 + i * 32);
|
||||||
|
hline->setInt("color", 0x444444FF);
|
||||||
|
m_gameIO->publish("render:debug:line", std::move(hline));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Diagonal line (animated)
|
||||||
|
{
|
||||||
|
auto line = std::make_unique<JsonDataNode>("line");
|
||||||
|
float offset = std::sin(m_time) * 50.0f;
|
||||||
|
line->setDouble("x1", 500);
|
||||||
|
line->setDouble("y1", 450 + offset);
|
||||||
|
line->setDouble("x2", 700);
|
||||||
|
line->setDouble("y2", 550 - offset);
|
||||||
|
line->setInt("color", 0xFFFF00FF); // Yellow
|
||||||
|
m_gameIO->publish("render:debug:line", std::move(line));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wireframe rectangle
|
||||||
|
{
|
||||||
|
auto rect = std::make_unique<JsonDataNode>("rect");
|
||||||
|
rect->setDouble("x", 750);
|
||||||
|
rect->setDouble("y", 400);
|
||||||
|
rect->setDouble("w", 100);
|
||||||
|
rect->setDouble("h", 80);
|
||||||
|
rect->setInt("color", 0x00FFFFFF); // Cyan
|
||||||
|
rect->setBool("filled", false);
|
||||||
|
m_gameIO->publish("render:debug:rect", std::move(rect));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Filled rectangle (pulsing)
|
||||||
|
{
|
||||||
|
float pulse = (std::sin(m_time * 2.0f) + 1.0f) * 0.5f;
|
||||||
|
uint8_t alpha = static_cast<uint8_t>(128 + pulse * 127);
|
||||||
|
uint32_t color = 0xFF4444FF | (alpha);
|
||||||
|
|
||||||
|
auto rect = std::make_unique<JsonDataNode>("rect");
|
||||||
|
rect->setDouble("x", 870);
|
||||||
|
rect->setDouble("y", 400);
|
||||||
|
rect->setDouble("w", 80);
|
||||||
|
rect->setDouble("h", 80);
|
||||||
|
rect->setInt("color", color);
|
||||||
|
rect->setBool("filled", true);
|
||||||
|
m_gameIO->publish("render:debug:rect", std::move(rect));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Crosshair at center
|
||||||
|
{
|
||||||
|
float cx = 512.0f, cy = 384.0f;
|
||||||
|
auto hline = std::make_unique<JsonDataNode>("line");
|
||||||
|
hline->setDouble("x1", cx - 20);
|
||||||
|
hline->setDouble("y1", cy);
|
||||||
|
hline->setDouble("x2", cx + 20);
|
||||||
|
hline->setDouble("y2", cy);
|
||||||
|
hline->setInt("color", 0xFF0000FF);
|
||||||
|
m_gameIO->publish("render:debug:line", std::move(hline));
|
||||||
|
|
||||||
|
auto vline = std::make_unique<JsonDataNode>("line");
|
||||||
|
vline->setDouble("x1", cx);
|
||||||
|
vline->setDouble("y1", cy - 20);
|
||||||
|
vline->setDouble("x2", cx);
|
||||||
|
vline->setDouble("y2", cy + 20);
|
||||||
|
vline->setInt("color", 0xFF0000FF);
|
||||||
|
m_gameIO->publish("render:debug:line", std::move(vline));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void spawnExplosion(float x, float y) {
|
||||||
|
std::uniform_real_distribution<float> angleDist(0.0f, 2.0f * 3.14159f);
|
||||||
|
std::uniform_real_distribution<float> speedDist(50.0f, 200.0f);
|
||||||
|
std::uniform_real_distribution<float> sizeDist(5.0f, 20.0f);
|
||||||
|
std::uniform_real_distribution<float> lifeDist(0.5f, 2.0f);
|
||||||
|
|
||||||
|
uint32_t colors[] = {
|
||||||
|
0xFF4444FF, 0xFF8844FF, 0xFFCC44FF, 0xFFFF44FF,
|
||||||
|
0xFF6644FF, 0xFFAA00FF
|
||||||
|
};
|
||||||
|
std::uniform_int_distribution<int> colorDist(0, 5);
|
||||||
|
|
||||||
|
for (int i = 0; i < 50; ++i) {
|
||||||
|
Particle p;
|
||||||
|
float angle = angleDist(m_rng);
|
||||||
|
float speed = speedDist(m_rng);
|
||||||
|
p.x = x;
|
||||||
|
p.y = y;
|
||||||
|
p.vx = std::cos(angle) * speed;
|
||||||
|
p.vy = std::sin(angle) * speed;
|
||||||
|
p.size = sizeDist(m_rng);
|
||||||
|
p.life = lifeDist(m_rng);
|
||||||
|
p.maxLife = p.life;
|
||||||
|
p.color = colors[colorDist(m_rng)];
|
||||||
|
p.alive = true;
|
||||||
|
|
||||||
|
// Find dead particle slot or add new
|
||||||
|
bool found = false;
|
||||||
|
for (auto& existing : m_particles) {
|
||||||
|
if (!existing.alive) {
|
||||||
|
existing = p;
|
||||||
|
found = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!found) {
|
||||||
|
m_particles.push_back(p);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
m_logger->info("Spawned explosion at ({:.0f}, {:.0f})", x, y);
|
||||||
|
}
|
||||||
|
|
||||||
|
void updateParticles(float dt) {
|
||||||
|
for (auto& p : m_particles) {
|
||||||
|
if (!p.alive) continue;
|
||||||
|
|
||||||
|
p.x += p.vx * dt;
|
||||||
|
p.y += p.vy * dt;
|
||||||
|
p.vy += 100.0f * dt; // Gravity
|
||||||
|
p.life -= dt;
|
||||||
|
|
||||||
|
if (p.life <= 0.0f) {
|
||||||
|
p.alive = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::shared_ptr<spdlog::logger> m_logger;
|
||||||
|
std::unique_ptr<BgfxRendererModule> m_renderer;
|
||||||
|
std::shared_ptr<IntraIO> m_rendererIOPtr; // Keep shared_ptr alive
|
||||||
|
std::shared_ptr<IntraIO> m_gameIOPtr; // Keep shared_ptr alive
|
||||||
|
IIO* m_rendererIO = nullptr; // Abstract interface
|
||||||
|
IIO* m_gameIO = nullptr; // Abstract interface
|
||||||
|
|
||||||
|
float m_time = 0.0f;
|
||||||
|
int m_frameCount = 0;
|
||||||
|
|
||||||
|
// Camera
|
||||||
|
float m_cameraX = 0.0f;
|
||||||
|
float m_cameraY = 0.0f;
|
||||||
|
float m_cameraVX = 0.0f;
|
||||||
|
float m_cameraVY = 0.0f;
|
||||||
|
float m_cameraZoom = 1.0f;
|
||||||
|
|
||||||
|
// Clear color
|
||||||
|
int m_clearColorIndex = 0;
|
||||||
|
|
||||||
|
// Particles
|
||||||
|
std::vector<Particle> m_particles;
|
||||||
|
std::mt19937 m_rng;
|
||||||
|
};
|
||||||
|
|
||||||
|
int main(int argc, char* argv[]) {
|
||||||
|
if (SDL_Init(SDL_INIT_VIDEO) < 0) {
|
||||||
|
std::cerr << "SDL_Init failed: " << SDL_GetError() << std::endl;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
SDL_Window* window = SDL_CreateWindow(
|
||||||
|
"BgfxRenderer Showcase",
|
||||||
|
SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED,
|
||||||
|
1024, 768,
|
||||||
|
SDL_WINDOW_SHOWN
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!window) {
|
||||||
|
std::cerr << "SDL_CreateWindow failed: " << SDL_GetError() << std::endl;
|
||||||
|
SDL_Quit();
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
RendererShowcase showcase;
|
||||||
|
if (!showcase.init(window)) {
|
||||||
|
SDL_DestroyWindow(window);
|
||||||
|
SDL_Quit();
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool running = true;
|
||||||
|
Uint64 lastTime = SDL_GetPerformanceCounter();
|
||||||
|
|
||||||
|
while (running) {
|
||||||
|
SDL_Event e;
|
||||||
|
while (SDL_PollEvent(&e)) {
|
||||||
|
if (e.type == SDL_QUIT ||
|
||||||
|
(e.type == SDL_KEYDOWN && e.key.keysym.sym == SDLK_ESCAPE)) {
|
||||||
|
running = false;
|
||||||
|
}
|
||||||
|
showcase.handleInput(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
Uint64 now = SDL_GetPerformanceCounter();
|
||||||
|
float dt = static_cast<float>(now - lastTime) / SDL_GetPerformanceFrequency();
|
||||||
|
lastTime = now;
|
||||||
|
|
||||||
|
showcase.update(dt);
|
||||||
|
showcase.render();
|
||||||
|
|
||||||
|
// Cap to ~60 FPS
|
||||||
|
SDL_Delay(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
Uint64 endTime = SDL_GetPerformanceCounter();
|
||||||
|
int frames = showcase.getFrameCount();
|
||||||
|
|
||||||
|
showcase.shutdown();
|
||||||
|
SDL_DestroyWindow(window);
|
||||||
|
SDL_Quit();
|
||||||
|
|
||||||
|
std::cout << "Rendered " << frames << " frames" << std::endl;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
221
tests/visual/test_single_button.cpp
Normal file
221
tests/visual/test_single_button.cpp
Normal file
@ -0,0 +1,221 @@
|
|||||||
|
/**
|
||||||
|
* Test: Single UIButton with interaction
|
||||||
|
* - Bouton rouge
|
||||||
|
* - Feedback visuel hover (orange)
|
||||||
|
* - Feedback visuel pressed (rouge foncé)
|
||||||
|
* - Logs des événements
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <SDL.h>
|
||||||
|
#include <SDL_syswm.h>
|
||||||
|
#include <iostream>
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
#include "BgfxRendererModule.h"
|
||||||
|
#include "UIModule/UIModule.h"
|
||||||
|
#include <grove/JsonDataNode.h>
|
||||||
|
#include <grove/IntraIOManager.h>
|
||||||
|
#include <grove/IntraIO.h>
|
||||||
|
#include <spdlog/spdlog.h>
|
||||||
|
#include <spdlog/sinks/stdout_color_sinks.h>
|
||||||
|
|
||||||
|
using namespace grove;
|
||||||
|
|
||||||
|
int main(int argc, char* argv[]) {
|
||||||
|
spdlog::set_level(spdlog::level::info);
|
||||||
|
auto logger = spdlog::stdout_color_mt("ButtonTest");
|
||||||
|
|
||||||
|
if (SDL_Init(SDL_INIT_VIDEO) < 0) {
|
||||||
|
std::cerr << "SDL_Init failed" << std::endl;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
SDL_Window* window = SDL_CreateWindow(
|
||||||
|
"Single Button Test",
|
||||||
|
SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED,
|
||||||
|
800, 600, SDL_WINDOW_SHOWN
|
||||||
|
);
|
||||||
|
|
||||||
|
SDL_SysWMinfo wmi;
|
||||||
|
SDL_VERSION(&wmi.version);
|
||||||
|
SDL_GetWindowWMInfo(window, &wmi);
|
||||||
|
|
||||||
|
// Create IIO instances
|
||||||
|
auto rendererIO = IntraIOManager::getInstance().createInstance("renderer");
|
||||||
|
auto uiIO = IntraIOManager::getInstance().createInstance("ui");
|
||||||
|
auto gameIO = IntraIOManager::getInstance().createInstance("game");
|
||||||
|
|
||||||
|
// Subscribe to UI events for logging
|
||||||
|
gameIO->subscribe("ui:hover");
|
||||||
|
gameIO->subscribe("ui:click");
|
||||||
|
gameIO->subscribe("ui:action");
|
||||||
|
|
||||||
|
// Initialize BgfxRenderer
|
||||||
|
auto renderer = std::make_unique<BgfxRendererModule>();
|
||||||
|
{
|
||||||
|
JsonDataNode config("config");
|
||||||
|
config.setDouble("nativeWindowHandle",
|
||||||
|
static_cast<double>(reinterpret_cast<uintptr_t>(wmi.info.win.window)));
|
||||||
|
config.setInt("windowWidth", 800);
|
||||||
|
config.setInt("windowHeight", 600);
|
||||||
|
renderer->setConfiguration(config, rendererIO.get(), nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize UIModule with ONE button - proper style structure
|
||||||
|
auto ui = std::make_unique<UIModule>();
|
||||||
|
{
|
||||||
|
JsonDataNode config("config");
|
||||||
|
config.setInt("windowWidth", 800);
|
||||||
|
config.setInt("windowHeight", 600);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* COMMENT CA MARCHE:
|
||||||
|
*
|
||||||
|
* 1. UIModule charge le layout JSON et crée les widgets
|
||||||
|
* 2. Chaque frame:
|
||||||
|
* - UIModule reçoit les events input (mouse, keyboard) via IIO
|
||||||
|
* - UIModule update les widgets (hover detection, click handling)
|
||||||
|
* - UIModule appelle render() sur chaque widget
|
||||||
|
* - UIButton::render() publie un sprite via IIO topic "render:sprite"
|
||||||
|
* - BgfxRenderer reçoit le sprite et le dessine
|
||||||
|
*
|
||||||
|
* 3. Les styles du bouton:
|
||||||
|
* - normal: état par défaut
|
||||||
|
* - hover: quand la souris est dessus
|
||||||
|
* - pressed: quand on clique
|
||||||
|
* - disabled: quand enabled=false
|
||||||
|
*
|
||||||
|
* 4. Les événements publiés par UIModule:
|
||||||
|
* - ui:hover - quand on entre/sort d'un widget
|
||||||
|
* - ui:click - quand on clique
|
||||||
|
* - ui:action - quand un bouton avec onClick est cliqué
|
||||||
|
*/
|
||||||
|
|
||||||
|
nlohmann::json layoutJson = {
|
||||||
|
{"id", "root"},
|
||||||
|
{"type", "panel"},
|
||||||
|
{"x", 0}, {"y", 0},
|
||||||
|
{"width", 800}, {"height", 600},
|
||||||
|
{"style", {
|
||||||
|
{"bgColor", "0x1A1A2EFF"} // Fond sombre
|
||||||
|
}},
|
||||||
|
{"children", {
|
||||||
|
{
|
||||||
|
{"id", "btn_test"},
|
||||||
|
{"type", "button"},
|
||||||
|
{"x", 250},
|
||||||
|
{"y", 250},
|
||||||
|
{"width", 300},
|
||||||
|
{"height", 100},
|
||||||
|
{"text", "CLICK ME!"},
|
||||||
|
{"onClick", "test_action"},
|
||||||
|
{"style", {
|
||||||
|
{"fontSize", 24.0},
|
||||||
|
{"normal", {
|
||||||
|
{"bgColor", "0xE74C3CFF"}, // Rouge
|
||||||
|
{"textColor", "0xFFFFFFFF"}, // Blanc
|
||||||
|
{"borderRadius", 8.0}
|
||||||
|
}},
|
||||||
|
{"hover", {
|
||||||
|
{"bgColor", "0xF39C12FF"}, // Orange (hover)
|
||||||
|
{"textColor", "0xFFFFFFFF"},
|
||||||
|
{"borderRadius", 8.0}
|
||||||
|
}},
|
||||||
|
{"pressed", {
|
||||||
|
{"bgColor", "0xC0392BFF"}, // Rouge foncé (pressed)
|
||||||
|
{"textColor", "0xFFFFFFFF"},
|
||||||
|
{"borderRadius", 8.0}
|
||||||
|
}}
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
};
|
||||||
|
|
||||||
|
auto layoutNode = std::make_unique<JsonDataNode>("layout", layoutJson);
|
||||||
|
config.setChild("layout", std::move(layoutNode));
|
||||||
|
|
||||||
|
ui->setConfiguration(config, uiIO.get(), nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
logger->info("=== SINGLE BUTTON TEST ===");
|
||||||
|
logger->info("- Bouton ROUGE au centre");
|
||||||
|
logger->info("- Hover = ORANGE");
|
||||||
|
logger->info("- Click = ROUGE FONCE");
|
||||||
|
logger->info("- Les events sont loggés ci-dessous");
|
||||||
|
logger->info("Press ESC to exit\n");
|
||||||
|
|
||||||
|
bool running = true;
|
||||||
|
while (running) {
|
||||||
|
SDL_Event e;
|
||||||
|
while (SDL_PollEvent(&e)) {
|
||||||
|
if (e.type == SDL_QUIT ||
|
||||||
|
(e.type == SDL_KEYDOWN && e.key.keysym.sym == SDLK_ESCAPE)) {
|
||||||
|
running = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Forward mouse events to UI via IIO
|
||||||
|
// IMPORTANT: Publish from gameIO (not uiIO) because IIO doesn't deliver to self
|
||||||
|
if (e.type == SDL_MOUSEMOTION) {
|
||||||
|
auto mouseMsg = std::make_unique<JsonDataNode>("mouse");
|
||||||
|
mouseMsg->setDouble("x", static_cast<double>(e.motion.x));
|
||||||
|
mouseMsg->setDouble("y", static_cast<double>(e.motion.y));
|
||||||
|
gameIO->publish("input:mouse:move", std::move(mouseMsg));
|
||||||
|
}
|
||||||
|
else if (e.type == SDL_MOUSEBUTTONDOWN || e.type == SDL_MOUSEBUTTONUP) {
|
||||||
|
auto mouseMsg = std::make_unique<JsonDataNode>("mouse");
|
||||||
|
mouseMsg->setInt("button", e.button.button);
|
||||||
|
mouseMsg->setBool("pressed", e.type == SDL_MOUSEBUTTONDOWN);
|
||||||
|
mouseMsg->setDouble("x", static_cast<double>(e.button.x));
|
||||||
|
mouseMsg->setDouble("y", static_cast<double>(e.button.y));
|
||||||
|
gameIO->publish("input:mouse:button", std::move(mouseMsg));
|
||||||
|
|
||||||
|
// Log press/release
|
||||||
|
logger->info("[INPUT] Mouse {} at ({}, {})",
|
||||||
|
e.type == SDL_MOUSEBUTTONDOWN ? "PRESSED" : "RELEASED",
|
||||||
|
e.button.x, e.button.y);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for UI events
|
||||||
|
while (gameIO->hasMessages() > 0) {
|
||||||
|
auto msg = gameIO->pullMessage();
|
||||||
|
|
||||||
|
if (msg.topic == "ui:hover") {
|
||||||
|
std::string widgetId = msg.data->getString("widgetId", "");
|
||||||
|
bool enter = msg.data->getBool("enter", false);
|
||||||
|
logger->info("[UI EVENT] HOVER {} widget '{}'",
|
||||||
|
enter ? "ENTER" : "LEAVE", widgetId);
|
||||||
|
}
|
||||||
|
else if (msg.topic == "ui:click") {
|
||||||
|
std::string widgetId = msg.data->getString("widgetId", "");
|
||||||
|
logger->info("[UI EVENT] CLICK on widget '{}'", widgetId);
|
||||||
|
}
|
||||||
|
else if (msg.topic == "ui:action") {
|
||||||
|
std::string action = msg.data->getString("action", "");
|
||||||
|
std::string widgetId = msg.data->getString("widgetId", "");
|
||||||
|
logger->info("[UI EVENT] ACTION '{}' from widget '{}'", action, widgetId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
JsonDataNode input("input");
|
||||||
|
input.setDouble("deltaTime", 0.016);
|
||||||
|
|
||||||
|
// Process UI (publishes sprites)
|
||||||
|
ui->process(input);
|
||||||
|
|
||||||
|
// Render
|
||||||
|
renderer->process(input);
|
||||||
|
|
||||||
|
SDL_Delay(16);
|
||||||
|
}
|
||||||
|
|
||||||
|
ui->shutdown();
|
||||||
|
renderer->shutdown();
|
||||||
|
IntraIOManager::getInstance().removeInstance("renderer");
|
||||||
|
IntraIOManager::getInstance().removeInstance("ui");
|
||||||
|
IntraIOManager::getInstance().removeInstance("game");
|
||||||
|
|
||||||
|
SDL_DestroyWindow(window);
|
||||||
|
SDL_Quit();
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
221
tests/visual/test_sprite_debug.cpp
Normal file
221
tests/visual/test_sprite_debug.cpp
Normal file
@ -0,0 +1,221 @@
|
|||||||
|
/**
|
||||||
|
* Diagnostic Test: Sprite Rendering Debug
|
||||||
|
*
|
||||||
|
* Ce test isole le problème de rendu des sprites UI en comparant:
|
||||||
|
* 1. Sprites hardcodés directement dans SpritePass
|
||||||
|
* 2. Sprites envoyés via IIO (comme UIRenderer le fait)
|
||||||
|
*
|
||||||
|
* Objectif: Identifier EXACTEMENT où le pipeline échoue
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <SDL.h>
|
||||||
|
#include <SDL_syswm.h>
|
||||||
|
#include <iostream>
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
#include "BgfxRendererModule.h"
|
||||||
|
#include <grove/JsonDataNode.h>
|
||||||
|
#include <grove/IntraIOManager.h>
|
||||||
|
#include <grove/IntraIO.h>
|
||||||
|
#include <spdlog/spdlog.h>
|
||||||
|
#include <spdlog/sinks/stdout_color_sinks.h>
|
||||||
|
|
||||||
|
using namespace grove;
|
||||||
|
|
||||||
|
class SpriteDiagnostic {
|
||||||
|
public:
|
||||||
|
bool init(SDL_Window* window) {
|
||||||
|
SDL_SysWMinfo wmi;
|
||||||
|
SDL_VERSION(&wmi.version);
|
||||||
|
SDL_GetWindowWMInfo(window, &wmi);
|
||||||
|
|
||||||
|
m_logger = spdlog::stdout_color_mt("SpriteDiag");
|
||||||
|
spdlog::set_level(spdlog::level::debug);
|
||||||
|
|
||||||
|
m_logger->info("=== DIAGNOSTIC: Sprite Rendering ===");
|
||||||
|
|
||||||
|
// Create IIO instances
|
||||||
|
m_rendererIO = IntraIOManager::getInstance().createInstance("renderer_diag");
|
||||||
|
m_testIO = IntraIOManager::getInstance().createInstance("test_publisher");
|
||||||
|
|
||||||
|
// Initialize renderer
|
||||||
|
m_renderer = std::make_unique<BgfxRendererModule>();
|
||||||
|
JsonDataNode config("config");
|
||||||
|
config.setDouble("nativeWindowHandle",
|
||||||
|
static_cast<double>(reinterpret_cast<uintptr_t>(wmi.info.win.window)));
|
||||||
|
config.setInt("windowWidth", 800);
|
||||||
|
config.setInt("windowHeight", 600);
|
||||||
|
config.setString("backend", "d3d11");
|
||||||
|
config.setBool("vsync", true);
|
||||||
|
m_renderer->setConfiguration(config, m_rendererIO.get(), nullptr);
|
||||||
|
|
||||||
|
m_logger->info("Renderer initialized");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void runTest() {
|
||||||
|
m_logger->info("\n========================================");
|
||||||
|
m_logger->info("TEST: Publishing sprites via IIO");
|
||||||
|
m_logger->info("========================================\n");
|
||||||
|
|
||||||
|
// Publish camera first (like UIModule does)
|
||||||
|
{
|
||||||
|
auto cam = std::make_unique<JsonDataNode>("camera");
|
||||||
|
cam->setDouble("x", 0.0);
|
||||||
|
cam->setDouble("y", 0.0);
|
||||||
|
cam->setDouble("zoom", 1.0);
|
||||||
|
cam->setInt("viewportX", 0);
|
||||||
|
cam->setInt("viewportY", 0);
|
||||||
|
cam->setInt("viewportW", 800);
|
||||||
|
cam->setInt("viewportH", 600);
|
||||||
|
m_testIO->publish("render:camera", std::move(cam));
|
||||||
|
m_logger->info("[PUB] Camera: viewport 800x600");
|
||||||
|
}
|
||||||
|
|
||||||
|
// TEST 1: Sprite at known position (should be visible)
|
||||||
|
{
|
||||||
|
auto sprite = std::make_unique<JsonDataNode>("sprite");
|
||||||
|
// Position at CENTER of screen (400, 300)
|
||||||
|
sprite->setDouble("x", 400.0); // Center X
|
||||||
|
sprite->setDouble("y", 300.0); // Center Y
|
||||||
|
sprite->setDouble("scaleX", 200.0); // 200px wide
|
||||||
|
sprite->setDouble("scaleY", 150.0); // 150px tall
|
||||||
|
sprite->setInt("color", 0xFF0000FF); // Red, full alpha
|
||||||
|
sprite->setInt("textureId", 0);
|
||||||
|
sprite->setInt("layer", 100);
|
||||||
|
|
||||||
|
m_logger->info("[PUB] Sprite 1: pos=({},{}) scale=({},{}) color=0x{:08X}",
|
||||||
|
400.0, 300.0, 200.0, 150.0, 0xFF0000FF);
|
||||||
|
|
||||||
|
m_testIO->publish("render:sprite", std::move(sprite));
|
||||||
|
}
|
||||||
|
|
||||||
|
// TEST 2: Sprite at top-left corner
|
||||||
|
{
|
||||||
|
auto sprite = std::make_unique<JsonDataNode>("sprite");
|
||||||
|
sprite->setDouble("x", 100.0);
|
||||||
|
sprite->setDouble("y", 100.0);
|
||||||
|
sprite->setDouble("scaleX", 100.0);
|
||||||
|
sprite->setDouble("scaleY", 100.0);
|
||||||
|
sprite->setInt("color", 0x00FF00FF); // Green
|
||||||
|
sprite->setInt("textureId", 0);
|
||||||
|
sprite->setInt("layer", 101);
|
||||||
|
|
||||||
|
m_logger->info("[PUB] Sprite 2: pos=({},{}) scale=({},{}) color=0x{:08X}",
|
||||||
|
100.0, 100.0, 100.0, 100.0, 0x00FF00FF);
|
||||||
|
|
||||||
|
m_testIO->publish("render:sprite", std::move(sprite));
|
||||||
|
}
|
||||||
|
|
||||||
|
// TEST 3: Sprite simulating UIButton (like UIRenderer::drawRect)
|
||||||
|
{
|
||||||
|
float btnX = 50.0f, btnY = 400.0f;
|
||||||
|
float btnW = 200.0f, btnH = 50.0f;
|
||||||
|
|
||||||
|
auto sprite = std::make_unique<JsonDataNode>("sprite");
|
||||||
|
// UIRenderer centers the sprite
|
||||||
|
sprite->setDouble("x", static_cast<double>(btnX + btnW * 0.5f));
|
||||||
|
sprite->setDouble("y", static_cast<double>(btnY + btnH * 0.5f));
|
||||||
|
sprite->setDouble("scaleX", static_cast<double>(btnW));
|
||||||
|
sprite->setDouble("scaleY", static_cast<double>(btnH));
|
||||||
|
sprite->setInt("color", 0x0984E3FF); // Blue (like primary button)
|
||||||
|
sprite->setInt("textureId", 0);
|
||||||
|
sprite->setInt("layer", 1000); // UI layer
|
||||||
|
|
||||||
|
m_logger->info("[PUB] Button Sprite: pos=({},{}) scale=({},{}) color=0x{:08X}",
|
||||||
|
btnX + btnW * 0.5f, btnY + btnH * 0.5f, btnW, btnH, 0x0984E3FF);
|
||||||
|
|
||||||
|
m_testIO->publish("render:sprite", std::move(sprite));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now render
|
||||||
|
m_logger->info("\n[RENDER] Processing frame...");
|
||||||
|
|
||||||
|
JsonDataNode input("input");
|
||||||
|
input.setDouble("deltaTime", 0.016);
|
||||||
|
input.setInt("frameCount", m_frameCount++);
|
||||||
|
|
||||||
|
m_renderer->process(input);
|
||||||
|
|
||||||
|
m_logger->info("[RENDER] Frame complete\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
void shutdown() {
|
||||||
|
m_renderer->shutdown();
|
||||||
|
IntraIOManager::getInstance().removeInstance("renderer_diag");
|
||||||
|
IntraIOManager::getInstance().removeInstance("test_publisher");
|
||||||
|
m_logger->info("Diagnostic test complete");
|
||||||
|
}
|
||||||
|
|
||||||
|
int getFrameCount() const { return m_frameCount; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::shared_ptr<spdlog::logger> m_logger;
|
||||||
|
std::unique_ptr<BgfxRendererModule> m_renderer;
|
||||||
|
std::shared_ptr<IntraIO> m_rendererIO;
|
||||||
|
std::shared_ptr<IntraIO> m_testIO;
|
||||||
|
int m_frameCount = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
int main(int argc, char* argv[]) {
|
||||||
|
if (SDL_Init(SDL_INIT_VIDEO) < 0) {
|
||||||
|
std::cerr << "SDL_Init failed: " << SDL_GetError() << std::endl;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
SDL_Window* window = SDL_CreateWindow(
|
||||||
|
"Sprite Rendering Diagnostic",
|
||||||
|
SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED,
|
||||||
|
800, 600,
|
||||||
|
SDL_WINDOW_SHOWN
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!window) {
|
||||||
|
std::cerr << "SDL_CreateWindow failed: " << SDL_GetError() << std::endl;
|
||||||
|
SDL_Quit();
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
SpriteDiagnostic diag;
|
||||||
|
if (!diag.init(window)) {
|
||||||
|
SDL_DestroyWindow(window);
|
||||||
|
SDL_Quit();
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::cout << "\n=== SPRITE DIAGNOSTIC TEST ===" << std::endl;
|
||||||
|
std::cout << "Expected: 3 colored rectangles" << std::endl;
|
||||||
|
std::cout << " - RED (200x150) at center of screen" << std::endl;
|
||||||
|
std::cout << " - GREEN (100x100) at top-left" << std::endl;
|
||||||
|
std::cout << " - BLUE (200x50) at bottom-left (simulating UI button)" << std::endl;
|
||||||
|
std::cout << "\nPress ESC to exit\n" << std::endl;
|
||||||
|
|
||||||
|
bool running = true;
|
||||||
|
int frameCount = 0;
|
||||||
|
const int maxFrames = 300; // Run for ~5 seconds
|
||||||
|
|
||||||
|
while (running && frameCount < maxFrames) {
|
||||||
|
SDL_Event e;
|
||||||
|
while (SDL_PollEvent(&e)) {
|
||||||
|
if (e.type == SDL_QUIT ||
|
||||||
|
(e.type == SDL_KEYDOWN && e.key.keysym.sym == SDLK_ESCAPE)) {
|
||||||
|
running = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
diag.runTest();
|
||||||
|
frameCount++;
|
||||||
|
|
||||||
|
SDL_Delay(16);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::cout << "\nRendered " << frameCount << " frames" << std::endl;
|
||||||
|
std::cout << "If you saw the colored rectangles: IIO routing works!" << std::endl;
|
||||||
|
std::cout << "If blank/red background only: Problem in SceneCollector or SpritePass" << std::endl;
|
||||||
|
|
||||||
|
diag.shutdown();
|
||||||
|
SDL_DestroyWindow(window);
|
||||||
|
SDL_Quit();
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
649
tests/visual/test_ui_showcase.cpp
Normal file
649
tests/visual/test_ui_showcase.cpp
Normal file
@ -0,0 +1,649 @@
|
|||||||
|
/**
|
||||||
|
* UIModule Complete Showcase
|
||||||
|
*
|
||||||
|
* Demonstrates ALL widgets of UIModule:
|
||||||
|
* - UIPanel (containers, backgrounds)
|
||||||
|
* - UIButton (click actions, hover states)
|
||||||
|
* - UILabel (static text, different sizes)
|
||||||
|
* - UICheckbox (toggle states)
|
||||||
|
* - UISlider (horizontal values)
|
||||||
|
* - UITextInput (text entry)
|
||||||
|
* - UIProgressBar (animated progress)
|
||||||
|
* - UIScrollPanel (scrollable content)
|
||||||
|
*
|
||||||
|
* Uses BgfxRenderer for rendering.
|
||||||
|
* Uses manual SDL input converted to IIO messages.
|
||||||
|
*
|
||||||
|
* Controls:
|
||||||
|
* - Mouse: Interact with UI widgets
|
||||||
|
* - ESC: Exit
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <SDL.h>
|
||||||
|
#include <SDL_syswm.h>
|
||||||
|
#include <iostream>
|
||||||
|
#include <cmath>
|
||||||
|
#include <memory>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
#include "BgfxRendererModule.h"
|
||||||
|
#include "UIModule.h"
|
||||||
|
#include <grove/JsonDataNode.h>
|
||||||
|
#include <grove/IntraIOManager.h>
|
||||||
|
#include <grove/IntraIO.h>
|
||||||
|
#include <spdlog/spdlog.h>
|
||||||
|
#include <spdlog/sinks/stdout_color_sinks.h>
|
||||||
|
#include <nlohmann/json.hpp>
|
||||||
|
|
||||||
|
using namespace grove;
|
||||||
|
|
||||||
|
// Create the UI layout JSON - must be a single root widget with children
|
||||||
|
static nlohmann::json createUILayout() {
|
||||||
|
nlohmann::json root;
|
||||||
|
|
||||||
|
// Root panel (full screen background)
|
||||||
|
root["type"] = "panel";
|
||||||
|
root["id"] = "root";
|
||||||
|
root["x"] = 0;
|
||||||
|
root["y"] = 0;
|
||||||
|
root["width"] = 1024;
|
||||||
|
root["height"] = 768;
|
||||||
|
root["style"] = {{"bgColor", "0x1a1a2eFF"}};
|
||||||
|
|
||||||
|
// Children array (like test_single_button)
|
||||||
|
nlohmann::json children = nlohmann::json::array();
|
||||||
|
|
||||||
|
// Title label
|
||||||
|
children.push_back({
|
||||||
|
{"type", "label"},
|
||||||
|
{"id", "title"},
|
||||||
|
{"x", 350}, {"y", 20},
|
||||||
|
{"width", 400}, {"height", 50},
|
||||||
|
{"text", "UIModule Showcase"},
|
||||||
|
{"style", {{"fontSize", 36}, {"color", "0xFFFFFFFF"}}}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Subtitle
|
||||||
|
children.push_back({
|
||||||
|
{"type", "label"},
|
||||||
|
{"id", "subtitle"},
|
||||||
|
{"x", 380}, {"y", 65},
|
||||||
|
{"width", 300}, {"height", 30},
|
||||||
|
{"text", "Interactive Widget Demo"},
|
||||||
|
{"style", {{"fontSize", 16}, {"color", "0x888888FF"}}}
|
||||||
|
});
|
||||||
|
|
||||||
|
// === LEFT COLUMN: Buttons Panel ===
|
||||||
|
children.push_back({
|
||||||
|
{"type", "panel"},
|
||||||
|
{"id", "buttons_panel"},
|
||||||
|
{"x", 30}, {"y", 120},
|
||||||
|
{"width", 300}, {"height", 280},
|
||||||
|
{"style", {{"bgColor", "0x2d3436FF"}}}
|
||||||
|
});
|
||||||
|
|
||||||
|
children.push_back({
|
||||||
|
{"type", "label"},
|
||||||
|
{"id", "buttons_title"},
|
||||||
|
{"x", 45}, {"y", 130},
|
||||||
|
{"width", 200}, {"height", 30},
|
||||||
|
{"text", "Buttons"},
|
||||||
|
{"style", {{"fontSize", 20}, {"color", "0xFFFFFFFF"}}}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Button 1 - Primary (with very contrasting hover for testing)
|
||||||
|
children.push_back({
|
||||||
|
{"type", "button"},
|
||||||
|
{"id", "btn_primary"},
|
||||||
|
{"x", 50}, {"y", 170},
|
||||||
|
{"width", 260}, {"height", 45},
|
||||||
|
{"text", "Primary Action"},
|
||||||
|
{"onClick", "action_primary"},
|
||||||
|
{"style", {
|
||||||
|
{"normal", {{"bgColor", "0x0984e3FF"}, {"textColor", "0xFFFFFFFF"}}},
|
||||||
|
{"hover", {{"bgColor", "0xFF0000FF"}, {"textColor", "0xFFFFFFFF"}}}, // BRIGHT RED
|
||||||
|
{"pressed", {{"bgColor", "0x00FF00FF"}, {"textColor", "0xFFFFFFFF"}}} // BRIGHT GREEN
|
||||||
|
}}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Button 2 - Success
|
||||||
|
children.push_back({
|
||||||
|
{"type", "button"},
|
||||||
|
{"id", "btn_success"},
|
||||||
|
{"x", 50}, {"y", 225},
|
||||||
|
{"width", 260}, {"height", 45},
|
||||||
|
{"text", "Success Button"},
|
||||||
|
{"onClick", "action_success"},
|
||||||
|
{"style", {
|
||||||
|
{"normal", {{"bgColor", "0x00b894FF"}, {"textColor", "0xFFFFFFFF"}}},
|
||||||
|
{"hover", {{"bgColor", "0x55efc4FF"}, {"textColor", "0xFFFFFFFF"}}},
|
||||||
|
{"pressed", {{"bgColor", "0x009874FF"}, {"textColor", "0xFFFFFFFF"}}}
|
||||||
|
}}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Button 3 - Warning
|
||||||
|
children.push_back({
|
||||||
|
{"type", "button"},
|
||||||
|
{"id", "btn_warning"},
|
||||||
|
{"x", 50}, {"y", 280},
|
||||||
|
{"width", 260}, {"height", 45},
|
||||||
|
{"text", "Warning Button"},
|
||||||
|
{"onClick", "action_warning"},
|
||||||
|
{"style", {
|
||||||
|
{"normal", {{"bgColor", "0xfdcb6eFF"}, {"textColor", "0x2d3436FF"}}},
|
||||||
|
{"hover", {{"bgColor", "0xffeaa7FF"}, {"textColor", "0x2d3436FF"}}},
|
||||||
|
{"pressed", {{"bgColor", "0xd4a84eFF"}, {"textColor", "0x2d3436FF"}}}
|
||||||
|
}}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Button 4 - Danger
|
||||||
|
children.push_back({
|
||||||
|
{"type", "button"},
|
||||||
|
{"id", "btn_danger"},
|
||||||
|
{"x", 50}, {"y", 335},
|
||||||
|
{"width", 260}, {"height", 45},
|
||||||
|
{"text", "Danger Button"},
|
||||||
|
{"onClick", "action_danger"},
|
||||||
|
{"style", {
|
||||||
|
{"normal", {{"bgColor", "0xd63031FF"}, {"textColor", "0xFFFFFFFF"}}},
|
||||||
|
{"hover", {{"bgColor", "0xff7675FF"}, {"textColor", "0xFFFFFFFF"}}},
|
||||||
|
{"pressed", {{"bgColor", "0xa62021FF"}, {"textColor", "0xFFFFFFFF"}}}
|
||||||
|
}}
|
||||||
|
});
|
||||||
|
|
||||||
|
// === MIDDLE COLUMN: Inputs Panel ===
|
||||||
|
children.push_back({
|
||||||
|
{"type", "panel"},
|
||||||
|
{"id", "inputs_panel"},
|
||||||
|
{"x", 362}, {"y", 120},
|
||||||
|
{"width", 300}, {"height", 280},
|
||||||
|
{"style", {{"bgColor", "0x2d3436FF"}}}
|
||||||
|
});
|
||||||
|
|
||||||
|
children.push_back({
|
||||||
|
{"type", "label"},
|
||||||
|
{"id", "inputs_title"},
|
||||||
|
{"x", 377}, {"y", 130},
|
||||||
|
{"width", 200}, {"height", 30},
|
||||||
|
{"text", "Input Widgets"},
|
||||||
|
{"style", {{"fontSize", 20}, {"color", "0xFFFFFFFF"}}}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Checkboxes
|
||||||
|
children.push_back({
|
||||||
|
{"type", "checkbox"},
|
||||||
|
{"id", "chk_option1"},
|
||||||
|
{"x", 382}, {"y", 175},
|
||||||
|
{"width", 200}, {"height", 24},
|
||||||
|
{"checked", false},
|
||||||
|
{"text", "Enable feature A"}
|
||||||
|
});
|
||||||
|
|
||||||
|
children.push_back({
|
||||||
|
{"type", "checkbox"},
|
||||||
|
{"id", "chk_option2"},
|
||||||
|
{"x", 382}, {"y", 210},
|
||||||
|
{"width", 200}, {"height", 24},
|
||||||
|
{"checked", true},
|
||||||
|
{"text", "Enable feature B"}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Slider
|
||||||
|
children.push_back({
|
||||||
|
{"type", "label"},
|
||||||
|
{"id", "slider_label"},
|
||||||
|
{"x", 382}, {"y", 255},
|
||||||
|
{"width", 200}, {"height", 20},
|
||||||
|
{"text", "Volume: 50%"},
|
||||||
|
{"style", {{"fontSize", 14}, {"color", "0xAAAAAAFF"}}}
|
||||||
|
});
|
||||||
|
|
||||||
|
children.push_back({
|
||||||
|
{"type", "slider"},
|
||||||
|
{"id", "volume_slider"},
|
||||||
|
{"x", 382}, {"y", 280},
|
||||||
|
{"width", 260}, {"height", 24},
|
||||||
|
{"min", 0.0},
|
||||||
|
{"max", 100.0},
|
||||||
|
{"value", 50.0}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Text input
|
||||||
|
children.push_back({
|
||||||
|
{"type", "label"},
|
||||||
|
{"id", "textinput_label"},
|
||||||
|
{"x", 382}, {"y", 320},
|
||||||
|
{"width", 200}, {"height", 20},
|
||||||
|
{"text", "Username:"},
|
||||||
|
{"style", {{"fontSize", 14}, {"color", "0xAAAAAAFF"}}}
|
||||||
|
});
|
||||||
|
|
||||||
|
children.push_back({
|
||||||
|
{"type", "textinput"},
|
||||||
|
{"id", "username_input"},
|
||||||
|
{"x", 382}, {"y", 345},
|
||||||
|
{"width", 260}, {"height", 35},
|
||||||
|
{"placeholder", "Enter username..."}
|
||||||
|
});
|
||||||
|
|
||||||
|
// === RIGHT COLUMN: Progress Panel ===
|
||||||
|
children.push_back({
|
||||||
|
{"type", "panel"},
|
||||||
|
{"id", "progress_panel"},
|
||||||
|
{"x", 694}, {"y", 120},
|
||||||
|
{"width", 300}, {"height", 280},
|
||||||
|
{"style", {{"bgColor", "0x2d3436FF"}}}
|
||||||
|
});
|
||||||
|
|
||||||
|
children.push_back({
|
||||||
|
{"type", "label"},
|
||||||
|
{"id", "progress_title"},
|
||||||
|
{"x", 709}, {"y", 130},
|
||||||
|
{"width", 200}, {"height", 30},
|
||||||
|
{"text", "Progress Bars"},
|
||||||
|
{"style", {{"fontSize", 20}, {"color", "0xFFFFFFFF"}}}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Progress bars
|
||||||
|
children.push_back({
|
||||||
|
{"type", "label"},
|
||||||
|
{"x", 714}, {"y", 170},
|
||||||
|
{"width", 200}, {"height", 20},
|
||||||
|
{"text", "Loading..."},
|
||||||
|
{"style", {{"fontSize", 14}, {"color", "0xAAAAAAFF"}}}
|
||||||
|
});
|
||||||
|
|
||||||
|
children.push_back({
|
||||||
|
{"type", "progressbar"},
|
||||||
|
{"id", "progress_loading"},
|
||||||
|
{"x", 714}, {"y", 195},
|
||||||
|
{"width", 260}, {"height", 20},
|
||||||
|
{"value", 0.35}
|
||||||
|
});
|
||||||
|
|
||||||
|
children.push_back({
|
||||||
|
{"type", "label"},
|
||||||
|
{"x", 714}, {"y", 230},
|
||||||
|
{"width", 200}, {"height", 20},
|
||||||
|
{"text", "Download: 75%"},
|
||||||
|
{"style", {{"fontSize", 14}, {"color", "0xAAAAAAFF"}}}
|
||||||
|
});
|
||||||
|
|
||||||
|
children.push_back({
|
||||||
|
{"type", "progressbar"},
|
||||||
|
{"id", "progress_download"},
|
||||||
|
{"x", 714}, {"y", 255},
|
||||||
|
{"width", 260}, {"height", 20},
|
||||||
|
{"value", 0.75}
|
||||||
|
});
|
||||||
|
|
||||||
|
children.push_back({
|
||||||
|
{"type", "label"},
|
||||||
|
{"x", 714}, {"y", 290},
|
||||||
|
{"width", 200}, {"height", 20},
|
||||||
|
{"text", "Health: 45%"},
|
||||||
|
{"style", {{"fontSize", 14}, {"color", "0xAAAAAAFF"}}}
|
||||||
|
});
|
||||||
|
|
||||||
|
children.push_back({
|
||||||
|
{"type", "progressbar"},
|
||||||
|
{"id", "progress_health"},
|
||||||
|
{"x", 714}, {"y", 315},
|
||||||
|
{"width", 260}, {"height", 25},
|
||||||
|
{"value", 0.45}
|
||||||
|
});
|
||||||
|
|
||||||
|
// === BOTTOM: Info Panel ===
|
||||||
|
children.push_back({
|
||||||
|
{"type", "panel"},
|
||||||
|
{"id", "info_panel"},
|
||||||
|
{"x", 30}, {"y", 420},
|
||||||
|
{"width", 964}, {"height", 320},
|
||||||
|
{"style", {{"bgColor", "0x2d3436FF"}}}
|
||||||
|
});
|
||||||
|
|
||||||
|
children.push_back({
|
||||||
|
{"type", "label"},
|
||||||
|
{"id", "info_title"},
|
||||||
|
{"x", 45}, {"y", 430},
|
||||||
|
{"width", 400}, {"height", 30},
|
||||||
|
{"text", "UI Showcase - Click buttons and interact!"},
|
||||||
|
{"style", {{"fontSize", 20}, {"color", "0xFFFFFFFF"}}}
|
||||||
|
});
|
||||||
|
|
||||||
|
children.push_back({
|
||||||
|
{"type", "label"},
|
||||||
|
{"x", 45}, {"y", 470},
|
||||||
|
{"width", 900}, {"height", 250},
|
||||||
|
{"text", "This showcase demonstrates UIModule widgets:\n- Panels (containers)\n- Labels (text)\n- Buttons (4 styles with hover/press states)\n- Checkboxes (toggles)\n- Sliders (value input)\n- Text Input (keyboard entry)\n- Progress Bars (3 examples)"},
|
||||||
|
{"style", {{"fontSize", 16}, {"color", "0xAAAAAAFF"}}}
|
||||||
|
});
|
||||||
|
|
||||||
|
root["children"] = children;
|
||||||
|
return root;
|
||||||
|
}
|
||||||
|
|
||||||
|
class UIShowcase {
|
||||||
|
public:
|
||||||
|
UIShowcase() = default;
|
||||||
|
|
||||||
|
bool init(SDL_Window* window) {
|
||||||
|
SDL_SysWMinfo wmi;
|
||||||
|
SDL_VERSION(&wmi.version);
|
||||||
|
SDL_GetWindowWMInfo(window, &wmi);
|
||||||
|
|
||||||
|
m_logger = spdlog::stdout_color_mt("UIShowcase");
|
||||||
|
spdlog::set_level(spdlog::level::info);
|
||||||
|
|
||||||
|
m_logger->info("=== UIModule Complete Showcase ===");
|
||||||
|
|
||||||
|
// Create IIO instances - keep shared_ptr alive, use IIO* abstract interface
|
||||||
|
// renderer: subscribes to render:* topics (from UI and game)
|
||||||
|
// ui: subscribes to input:* topics, publishes ui:* events
|
||||||
|
// input: publishes input:* topics
|
||||||
|
// game: subscribes to ui:* events, can publish render:* directly
|
||||||
|
m_rendererIOPtr = IntraIOManager::getInstance().createInstance("renderer");
|
||||||
|
m_uiIOPtr = IntraIOManager::getInstance().createInstance("ui_module");
|
||||||
|
m_inputIOPtr = IntraIOManager::getInstance().createInstance("input");
|
||||||
|
m_gameIOPtr = IntraIOManager::getInstance().createInstance("game");
|
||||||
|
m_rendererIO = m_rendererIOPtr.get();
|
||||||
|
m_uiIO = m_uiIOPtr.get();
|
||||||
|
m_inputIO = m_inputIOPtr.get();
|
||||||
|
m_gameIO = m_gameIOPtr.get();
|
||||||
|
|
||||||
|
// Create and configure BgfxRenderer
|
||||||
|
m_renderer = std::make_unique<BgfxRendererModule>();
|
||||||
|
{
|
||||||
|
JsonDataNode config("config");
|
||||||
|
config.setDouble("nativeWindowHandle",
|
||||||
|
static_cast<double>(reinterpret_cast<uintptr_t>(wmi.info.win.window)));
|
||||||
|
config.setInt("windowWidth", 1024);
|
||||||
|
config.setInt("windowHeight", 768);
|
||||||
|
config.setString("backend", "d3d11");
|
||||||
|
config.setBool("vsync", true);
|
||||||
|
m_renderer->setConfiguration(config, m_rendererIO, nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create and configure UIModule with inline layout
|
||||||
|
m_uiModule = std::make_unique<UIModule>();
|
||||||
|
{
|
||||||
|
nlohmann::json layoutJson = createUILayout();
|
||||||
|
JsonDataNode config("config", layoutJson);
|
||||||
|
config.setInt("windowWidth", 1024);
|
||||||
|
config.setInt("windowHeight", 768);
|
||||||
|
config.setInt("baseLayer", 1000);
|
||||||
|
|
||||||
|
// Add layout as child
|
||||||
|
auto layoutNode = std::make_unique<JsonDataNode>("layout", layoutJson);
|
||||||
|
config.setChild("layout", std::move(layoutNode));
|
||||||
|
|
||||||
|
m_uiModule->setConfiguration(config, m_uiIO, nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Subscribe game to UI events
|
||||||
|
m_gameIO->subscribe("ui:action");
|
||||||
|
m_gameIO->subscribe("ui:click");
|
||||||
|
m_gameIO->subscribe("ui:value_changed");
|
||||||
|
m_gameIO->subscribe("ui:text_changed");
|
||||||
|
m_gameIO->subscribe("ui:text_submit");
|
||||||
|
m_gameIO->subscribe("ui:hover");
|
||||||
|
|
||||||
|
m_logger->info("Modules initialized");
|
||||||
|
m_logger->info("Controls: Mouse to interact, ESC to exit");
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void handleSDLEvent(SDL_Event& e) {
|
||||||
|
// Convert SDL events to IIO input messages
|
||||||
|
// IMPORTANT: Publish from m_gameIO (not m_inputIO or m_uiIO) because IIO doesn't deliver to self
|
||||||
|
if (e.type == SDL_MOUSEMOTION) {
|
||||||
|
auto msg = std::make_unique<JsonDataNode>("mouse");
|
||||||
|
msg->setDouble("x", static_cast<double>(e.motion.x));
|
||||||
|
msg->setDouble("y", static_cast<double>(e.motion.y));
|
||||||
|
m_gameIO->publish("input:mouse:move", std::move(msg));
|
||||||
|
}
|
||||||
|
else if (e.type == SDL_MOUSEBUTTONDOWN || e.type == SDL_MOUSEBUTTONUP) {
|
||||||
|
auto msg = std::make_unique<JsonDataNode>("mouse");
|
||||||
|
msg->setInt("button", e.button.button - 1); // SDL uses 1-based
|
||||||
|
msg->setBool("pressed", e.type == SDL_MOUSEBUTTONDOWN);
|
||||||
|
msg->setDouble("x", static_cast<double>(e.button.x));
|
||||||
|
msg->setDouble("y", static_cast<double>(e.button.y));
|
||||||
|
m_gameIO->publish("input:mouse:button", std::move(msg));
|
||||||
|
}
|
||||||
|
else if (e.type == SDL_MOUSEWHEEL) {
|
||||||
|
auto msg = std::make_unique<JsonDataNode>("wheel");
|
||||||
|
msg->setDouble("delta", static_cast<double>(e.wheel.y));
|
||||||
|
m_gameIO->publish("input:mouse:wheel", std::move(msg));
|
||||||
|
}
|
||||||
|
else if (e.type == SDL_KEYDOWN) {
|
||||||
|
auto msg = std::make_unique<JsonDataNode>("key");
|
||||||
|
msg->setInt("keyCode", e.key.keysym.sym);
|
||||||
|
msg->setInt("scancode", e.key.keysym.scancode);
|
||||||
|
msg->setBool("pressed", true);
|
||||||
|
msg->setInt("char", 0);
|
||||||
|
m_gameIO->publish("input:keyboard", std::move(msg));
|
||||||
|
}
|
||||||
|
else if (e.type == SDL_TEXTINPUT) {
|
||||||
|
auto msg = std::make_unique<JsonDataNode>("key");
|
||||||
|
msg->setInt("keyCode", 0);
|
||||||
|
msg->setBool("pressed", true);
|
||||||
|
msg->setInt("char", static_cast<int>(e.text.text[0]));
|
||||||
|
m_gameIO->publish("input:keyboard", std::move(msg));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void update(float dt) {
|
||||||
|
m_time += dt;
|
||||||
|
m_frameCount++;
|
||||||
|
|
||||||
|
// Process UI events from game perspective
|
||||||
|
processUIEvents();
|
||||||
|
|
||||||
|
// Animate progress bars
|
||||||
|
animateProgressBars(dt);
|
||||||
|
}
|
||||||
|
|
||||||
|
void render() {
|
||||||
|
// Set camera for full window
|
||||||
|
{
|
||||||
|
auto cam = std::make_unique<JsonDataNode>("camera");
|
||||||
|
cam->setDouble("x", 0.0);
|
||||||
|
cam->setDouble("y", 0.0);
|
||||||
|
cam->setDouble("zoom", 1.0);
|
||||||
|
cam->setInt("viewportX", 0);
|
||||||
|
cam->setInt("viewportY", 0);
|
||||||
|
cam->setInt("viewportW", 1024);
|
||||||
|
cam->setInt("viewportH", 768);
|
||||||
|
m_gameIO->publish("render:camera", std::move(cam));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Process modules in order: UI first (to handle input and prepare render commands)
|
||||||
|
JsonDataNode input("input");
|
||||||
|
input.setDouble("deltaTime", 0.016);
|
||||||
|
input.setInt("frameCount", m_frameCount);
|
||||||
|
|
||||||
|
m_uiModule->process(input);
|
||||||
|
m_renderer->process(input);
|
||||||
|
}
|
||||||
|
|
||||||
|
void shutdown() {
|
||||||
|
m_uiModule->shutdown();
|
||||||
|
m_renderer->shutdown();
|
||||||
|
|
||||||
|
IntraIOManager::getInstance().removeInstance("renderer");
|
||||||
|
IntraIOManager::getInstance().removeInstance("ui_module");
|
||||||
|
IntraIOManager::getInstance().removeInstance("input");
|
||||||
|
IntraIOManager::getInstance().removeInstance("game");
|
||||||
|
|
||||||
|
m_logger->info("UIShowcase shutdown complete");
|
||||||
|
}
|
||||||
|
|
||||||
|
int getFrameCount() const { return m_frameCount; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
void processUIEvents() {
|
||||||
|
while (m_gameIO->hasMessages() > 0) {
|
||||||
|
auto msg = m_gameIO->pullMessage();
|
||||||
|
|
||||||
|
std::string logEntry;
|
||||||
|
|
||||||
|
if (msg.topic == "ui:action") {
|
||||||
|
std::string action = msg.data->getString("action", "");
|
||||||
|
std::string widgetId = msg.data->getString("widgetId", "");
|
||||||
|
logEntry = "Action: " + action + " (" + widgetId + ")";
|
||||||
|
|
||||||
|
// Handle specific actions
|
||||||
|
if (action == "action_primary") {
|
||||||
|
m_logger->info("Primary button clicked!");
|
||||||
|
}
|
||||||
|
else if (action == "action_danger") {
|
||||||
|
m_logger->warn("Danger button clicked!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (msg.topic == "ui:click") {
|
||||||
|
std::string widgetId = msg.data->getString("widgetId", "");
|
||||||
|
double x = msg.data->getDouble("x", 0);
|
||||||
|
double y = msg.data->getDouble("y", 0);
|
||||||
|
logEntry = "Click: " + widgetId + " at (" +
|
||||||
|
std::to_string(static_cast<int>(x)) + "," +
|
||||||
|
std::to_string(static_cast<int>(y)) + ")";
|
||||||
|
}
|
||||||
|
else if (msg.topic == "ui:value_changed") {
|
||||||
|
std::string widgetId = msg.data->getString("widgetId", "");
|
||||||
|
if (widgetId == "volume_slider") {
|
||||||
|
double value = msg.data->getDouble("value", 0);
|
||||||
|
logEntry = "Volume: " + std::to_string(static_cast<int>(value)) + "%";
|
||||||
|
}
|
||||||
|
else if (widgetId.find("chk_") == 0) {
|
||||||
|
bool checked = msg.data->getBool("checked", false);
|
||||||
|
logEntry = widgetId + " = " + (checked ? "ON" : "OFF");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (msg.topic == "ui:text_submit") {
|
||||||
|
std::string widgetId = msg.data->getString("widgetId", "");
|
||||||
|
std::string text = msg.data->getString("text", "");
|
||||||
|
logEntry = "Submit: " + widgetId + " = \"" + text + "\"";
|
||||||
|
}
|
||||||
|
else if (msg.topic == "ui:hover") {
|
||||||
|
std::string widgetId = msg.data->getString("widgetId", "");
|
||||||
|
bool enter = msg.data->getBool("enter", false);
|
||||||
|
if (enter && !widgetId.empty()) {
|
||||||
|
logEntry = "Hover: " + widgetId;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add to log if we have an entry
|
||||||
|
if (!logEntry.empty()) {
|
||||||
|
addLogEntry(logEntry);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void addLogEntry(const std::string& entry) {
|
||||||
|
// Shift log entries up
|
||||||
|
for (int i = 7; i > 0; --i) {
|
||||||
|
m_logEntries[i] = m_logEntries[i - 1];
|
||||||
|
}
|
||||||
|
m_logEntries[0] = entry;
|
||||||
|
|
||||||
|
m_logger->info("UI Event: {}", entry);
|
||||||
|
}
|
||||||
|
|
||||||
|
void animateProgressBars(float dt) {
|
||||||
|
// Animate loading progress (loops)
|
||||||
|
m_loadingProgress += dt * 0.3f;
|
||||||
|
if (m_loadingProgress > 1.0f) {
|
||||||
|
m_loadingProgress = 0.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
// We would update progress bars here if we had a way to modify widgets
|
||||||
|
// For now, this is just simulation - the actual values are set in layout
|
||||||
|
}
|
||||||
|
|
||||||
|
std::shared_ptr<spdlog::logger> m_logger;
|
||||||
|
std::unique_ptr<BgfxRendererModule> m_renderer;
|
||||||
|
std::unique_ptr<UIModule> m_uiModule;
|
||||||
|
// Keep shared_ptr alive
|
||||||
|
std::shared_ptr<IntraIO> m_rendererIOPtr;
|
||||||
|
std::shared_ptr<IntraIO> m_uiIOPtr;
|
||||||
|
std::shared_ptr<IntraIO> m_inputIOPtr;
|
||||||
|
std::shared_ptr<IntraIO> m_gameIOPtr;
|
||||||
|
// Abstract IIO* interface
|
||||||
|
IIO* m_rendererIO = nullptr;
|
||||||
|
IIO* m_uiIO = nullptr;
|
||||||
|
IIO* m_inputIO = nullptr;
|
||||||
|
IIO* m_gameIO = nullptr;
|
||||||
|
|
||||||
|
float m_time = 0.0f;
|
||||||
|
int m_frameCount = 0;
|
||||||
|
|
||||||
|
// Progress animation
|
||||||
|
float m_loadingProgress = 0.0f;
|
||||||
|
|
||||||
|
// Event log
|
||||||
|
std::string m_logEntries[8];
|
||||||
|
};
|
||||||
|
|
||||||
|
int main(int argc, char* argv[]) {
|
||||||
|
if (SDL_Init(SDL_INIT_VIDEO) < 0) {
|
||||||
|
std::cerr << "SDL_Init failed: " << SDL_GetError() << std::endl;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Enable text input for text fields
|
||||||
|
SDL_StartTextInput();
|
||||||
|
|
||||||
|
SDL_Window* window = SDL_CreateWindow(
|
||||||
|
"UIModule Showcase",
|
||||||
|
SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED,
|
||||||
|
1024, 768,
|
||||||
|
SDL_WINDOW_SHOWN
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!window) {
|
||||||
|
std::cerr << "SDL_CreateWindow failed: " << SDL_GetError() << std::endl;
|
||||||
|
SDL_Quit();
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
UIShowcase showcase;
|
||||||
|
if (!showcase.init(window)) {
|
||||||
|
SDL_DestroyWindow(window);
|
||||||
|
SDL_Quit();
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool running = true;
|
||||||
|
Uint64 lastTime = SDL_GetPerformanceCounter();
|
||||||
|
|
||||||
|
while (running) {
|
||||||
|
SDL_Event e;
|
||||||
|
while (SDL_PollEvent(&e)) {
|
||||||
|
if (e.type == SDL_QUIT ||
|
||||||
|
(e.type == SDL_KEYDOWN && e.key.keysym.sym == SDLK_ESCAPE)) {
|
||||||
|
running = false;
|
||||||
|
}
|
||||||
|
showcase.handleSDLEvent(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
Uint64 now = SDL_GetPerformanceCounter();
|
||||||
|
float dt = static_cast<float>(now - lastTime) / SDL_GetPerformanceFrequency();
|
||||||
|
lastTime = now;
|
||||||
|
|
||||||
|
showcase.update(dt);
|
||||||
|
showcase.render();
|
||||||
|
|
||||||
|
SDL_Delay(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
SDL_StopTextInput();
|
||||||
|
|
||||||
|
int frames = showcase.getFrameCount();
|
||||||
|
showcase.shutdown();
|
||||||
|
SDL_DestroyWindow(window);
|
||||||
|
SDL_Quit();
|
||||||
|
|
||||||
|
std::cout << "Rendered " << frames << " frames" << std::endl;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue
Block a user