/** * 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 #include #include #include #include #include #include #include "BgfxRendererModule.h" #include #include #include #include #include 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(); JsonDataNode config("config"); config.setDouble("nativeWindowHandle", static_cast(reinterpret_cast(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("clear"); clear->setInt("color", clearColors[m_clearColorIndex]); m_gameIO->publish("render:clear", std::move(clear)); } void sendCamera() { auto cam = std::make_unique("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("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("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("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("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("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("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("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("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("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("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("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("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("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("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("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("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("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(128 + pulse * 127); uint32_t color = 0xFF4444FF | (alpha); auto rect = std::make_unique("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("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("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 angleDist(0.0f, 2.0f * 3.14159f); std::uniform_real_distribution speedDist(50.0f, 200.0f); std::uniform_real_distribution sizeDist(5.0f, 20.0f); std::uniform_real_distribution lifeDist(0.5f, 2.0f); uint32_t colors[] = { 0xFF4444FF, 0xFF8844FF, 0xFFCC44FF, 0xFFFF44FF, 0xFF6644FF, 0xFFAA00FF }; std::uniform_int_distribution 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 m_logger; std::unique_ptr m_renderer; std::shared_ptr m_rendererIOPtr; // Keep shared_ptr alive std::shared_ptr 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 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(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; }