GroveEngine/tests/visual/test_3buttons_minimal.cpp
StillHammer 63751d6f91 fix: Multi-texture sprite rendering - setState per batch + transient buffers
Fixed two critical bugs preventing multiple textured sprites from rendering correctly:

1. **setState consumed by submit**: Render state was set once at the beginning,
   but bgfx consumes state at each submit(). Batches 2+ had no state → invisible.

   FIX: Call setState() before EACH batch, not once globally.

2. **Buffer overwrite race condition**: updateBuffer() is immediate but submit()
   is deferred. When batch 2 called updateBuffer(), it overwrote batch 1's data
   BEFORE bgfx executed the draw calls. All batches used the last batch's data
   → all sprites rendered at the same position (superimposed).

   FIX: Use transient buffers (one per batch, frame-local) instead of reusing
   the same dynamic buffer. Each batch gets its own isolated memory.

Changes:
- SpritePass: setState before each batch + transient buffer allocation per batch
- UIRenderer: Retained mode rendering (render:sprite:add/update/remove)
- test_ui_showcase: Added 3 textured buttons demo section
- test_3buttons_minimal: Minimal test case for multi-texture debugging

Tested: 3 textured buttons now render at correct positions with correct textures.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-01-14 14:05:56 +07:00

212 lines
8.0 KiB
C++

/**
* Test: UIButton avec texture PNG chargée depuis un fichier
* Ce test montre qu'on peut mettre une VRAIE image sur un bouton
*/
#include <SDL.h>
#include <SDL_syswm.h>
#include <iostream>
#include <memory>
#include <fstream>
#include "BgfxRendererModule.h"
#include "UIModule/UIModule.h"
#include "../modules/BgfxRenderer/Resources/ResourceCache.h"
#include "../modules/BgfxRenderer/RHI/RHITypes.h"
#include "../modules/BgfxRenderer/RHI/RHIDevice.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 <bgfx/bgfx.h>
#include <vector>
using namespace grove;
int main(int argc, char* argv[]) {
spdlog::set_level(spdlog::level::info);
auto logger = spdlog::stdout_color_mt("TexturedButtonTest");
if (SDL_Init(SDL_INIT_VIDEO) < 0) {
std::cerr << "SDL_Init failed" << std::endl;
return 1;
}
SDL_Window* window = SDL_CreateWindow(
"Textured Button Test - Gradient",
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 - IMPORTANT: game publishes input, ui subscribes and publishes render commands
auto gameIO = IntraIOManager::getInstance().createInstance("game");
auto uiIO = IntraIOManager::getInstance().createInstance("ui");
auto rendererIO = IntraIOManager::getInstance().createInstance("renderer");
gameIO->subscribe("ui:hover");
gameIO->subscribe("ui:click");
gameIO->subscribe("ui:action");
// Initialize BgfxRenderer WITH 3 TEXTURES loaded via config
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);
// Load 3 textures
config.setString("texture1", "../../assets/textures/5oxaxt1vo2f91.jpg"); // Car
config.setString("texture2", "../../assets/textures/1f440.png"); // Eyes
config.setString("texture3", "../../assets/textures/IconDesigner.png"); // Icon
renderer->setConfiguration(config, rendererIO.get(), nullptr);
}
logger->info("✓ Loaded 3 textures (IDs: 1, 2, 3)");
// Initialize UIModule with 3 TEXTURED BUTTONS
auto ui = std::make_unique<UIModule>();
{
JsonDataNode config("config");
config.setInt("windowWidth", 800);
config.setInt("windowHeight", 600);
nlohmann::json layoutJson = {
{"id", "root"},
{"type", "panel"},
{"x", 0}, {"y", 0},
{"width", 800}, {"height", 600}, // Full screen invisible panel (just container)
{"style", {
{"bgColor", "0x00000000"} // Fully transparent - just a container
}},
{"children", {
{
{"id", "btn_car"},
{"type", "button"},
{"x", 50},
{"y", 50},
{"width", 400},
{"height", 200},
{"text", ""},
{"onClick", "car_action"},
{"style", {
{"normal", {{"textureId", 1}, {"bgColor", "0xFFFFFFFF"}}},
{"hover", {{"textureId", 1}, {"bgColor", "0xFFFF00FF"}}},
{"pressed", {{"textureId", 1}, {"bgColor", "0x888888FF"}}}
}}
},
{
{"id", "btn_eyes"},
{"type", "button"},
{"x", 50},
{"y", 270},
{"width", 250},
{"height", 200},
{"text", ""},
{"onClick", "eyes_action"},
{"style", {
{"normal", {{"textureId", 2}, {"bgColor", "0xFFFFFFFF"}}},
{"hover", {{"textureId", 2}, {"bgColor", "0x00FFFFFF"}}},
{"pressed", {{"textureId", 2}, {"bgColor", "0x888888FF"}}}
}}
},
{
{"id", "btn_icon"},
{"type", "button"},
{"x", 320},
{"y", 270},
{"width", 250},
{"height", 200},
{"text", ""},
{"onClick", "icon_action"},
{"style", {
{"normal", {{"textureId", 3}, {"bgColor", "0xFFFFFFFF"}}},
{"hover", {{"textureId", 3}, {"bgColor", "0xFF00FFFF"}}},
{"pressed", {{"textureId", 3}, {"bgColor", "0x888888FF"}}}
}}
}
}}
};
auto layoutNode = std::make_unique<JsonDataNode>("layout", layoutJson);
config.setChild("layout", std::move(layoutNode));
ui->setConfiguration(config, uiIO.get(), nullptr);
logger->info("✓ UIModule configured with 3 textured buttons!");
}
logger->info("\n╔════════════════════════════════════════╗");
logger->info("║ 3 BOUTONS AVEC TEXTURES ║");
logger->info("╠════════════════════════════════════════╣");
logger->info("║ Button 1: Car (textureId=1) ║");
logger->info("║ Button 2: Eyes (textureId=2) ║");
logger->info("║ Button 3: Icon (textureId=3) ║");
logger->info("║ Press ESC to exit ║");
logger->info("╚════════════════════════════════════════╝\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
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));
}
}
// Check for UI events
while (gameIO->hasMessages() > 0) {
auto msg = gameIO->pullMessage();
if (msg.topic == "ui:action") {
logger->info("🖱️ BOUTON CLICKÉ!");
}
}
// Update modules
JsonDataNode input("input");
input.setDouble("deltaTime", 0.016);
ui->process(input);
renderer->process(input);
SDL_Delay(16);
}
logger->info("Cleaning up...");
// Textures are managed by ResourceCache, will be cleaned up in renderer->shutdown()
ui->shutdown();
renderer->shutdown();
IntraIOManager::getInstance().removeInstance("game");
IntraIOManager::getInstance().removeInstance("ui");
IntraIOManager::getInstance().removeInstance("renderer");
SDL_DestroyWindow(window);
SDL_Quit();
logger->info("Test complete!");
return 0;
}