Phase 2 - RHI Layer: - Fix Command struct default constructor for union with non-trivial types - Add missing mutex includes in ResourceCache.cpp - Fix const_cast for getChildReadOnly in SceneCollector Phase 3 - Shaders & Visual Test: - Add ShaderManager for centralized shader loading - Embed pre-compiled shaders (OpenGL, Vulkan, DX11, Metal) - Add test_20_bgfx_rhi: 23 unit tests for RHI components - Add test_21_bgfx_triangle: visual test rendering colored triangle Test results: - RHI unit tests: 23/23 passing - Visual test: ~567 FPS with Vulkan renderer 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
333 lines
10 KiB
C++
333 lines
10 KiB
C++
/**
|
|
* Test: BgfxRenderer Visual Triangle Test
|
|
*
|
|
* Renders a colored triangle using bgfx with SDL2 windowing.
|
|
* This validates the full rendering pipeline including shaders.
|
|
*/
|
|
|
|
#include <SDL2/SDL.h>
|
|
#include <SDL2/SDL_syswm.h>
|
|
|
|
#include <bgfx/bgfx.h>
|
|
#include <bgfx/platform.h>
|
|
#include <bx/math.h>
|
|
|
|
#include <iostream>
|
|
#include <cstdint>
|
|
|
|
// ============================================================================
|
|
// Embedded Shaders (from bgfx examples - drawstress)
|
|
// ============================================================================
|
|
|
|
// Include pre-compiled shader bytecode for all platforms
|
|
#include "../../modules/BgfxRenderer/Shaders/vs_color.bin.h"
|
|
#include "../../modules/BgfxRenderer/Shaders/fs_color.bin.h"
|
|
|
|
// ============================================================================
|
|
// Shader loading helper
|
|
// ============================================================================
|
|
|
|
static const bgfx::Memory* loadShaderMem(const uint8_t* data, uint32_t size) {
|
|
return bgfx::copy(data, size);
|
|
}
|
|
|
|
bgfx::ShaderHandle loadShader(bgfx::RendererType::Enum type, const char* name) {
|
|
const uint8_t* data = nullptr;
|
|
uint32_t size = 0;
|
|
|
|
// Select shader based on renderer type
|
|
bool isVertex = (name[0] == 'v');
|
|
|
|
switch (type) {
|
|
case bgfx::RendererType::OpenGL:
|
|
if (isVertex) {
|
|
data = vs_drawstress_glsl;
|
|
size = sizeof(vs_drawstress_glsl);
|
|
} else {
|
|
data = fs_drawstress_glsl;
|
|
size = sizeof(fs_drawstress_glsl);
|
|
}
|
|
break;
|
|
|
|
case bgfx::RendererType::OpenGLES:
|
|
if (isVertex) {
|
|
data = vs_drawstress_essl;
|
|
size = sizeof(vs_drawstress_essl);
|
|
} else {
|
|
data = fs_drawstress_essl;
|
|
size = sizeof(fs_drawstress_essl);
|
|
}
|
|
break;
|
|
|
|
case bgfx::RendererType::Vulkan:
|
|
if (isVertex) {
|
|
data = vs_drawstress_spv;
|
|
size = sizeof(vs_drawstress_spv);
|
|
} else {
|
|
data = fs_drawstress_spv;
|
|
size = sizeof(fs_drawstress_spv);
|
|
}
|
|
break;
|
|
|
|
case bgfx::RendererType::Direct3D11:
|
|
case bgfx::RendererType::Direct3D12:
|
|
if (isVertex) {
|
|
data = vs_drawstress_dx11;
|
|
size = sizeof(vs_drawstress_dx11);
|
|
} else {
|
|
data = fs_drawstress_dx11;
|
|
size = sizeof(fs_drawstress_dx11);
|
|
}
|
|
break;
|
|
|
|
case bgfx::RendererType::Metal:
|
|
if (isVertex) {
|
|
data = vs_drawstress_mtl;
|
|
size = sizeof(vs_drawstress_mtl);
|
|
} else {
|
|
data = fs_drawstress_mtl;
|
|
size = sizeof(fs_drawstress_mtl);
|
|
}
|
|
break;
|
|
|
|
default:
|
|
std::cerr << "Unsupported renderer type for shaders\n";
|
|
return BGFX_INVALID_HANDLE;
|
|
}
|
|
|
|
return bgfx::createShader(loadShaderMem(data, size));
|
|
}
|
|
|
|
// ============================================================================
|
|
// Vertex Data
|
|
// ============================================================================
|
|
|
|
struct PosColorVertex {
|
|
float x, y, z;
|
|
uint32_t abgr;
|
|
|
|
static bgfx::VertexLayout layout;
|
|
|
|
static void init() {
|
|
layout
|
|
.begin()
|
|
.add(bgfx::Attrib::Position, 3, bgfx::AttribType::Float)
|
|
.add(bgfx::Attrib::Color0, 4, bgfx::AttribType::Uint8, true)
|
|
.end();
|
|
}
|
|
};
|
|
|
|
bgfx::VertexLayout PosColorVertex::layout;
|
|
|
|
// Triangle vertices (colored) - centered at origin
|
|
static PosColorVertex s_triangleVertices[] = {
|
|
{ -0.5f, -0.5f, 0.0f, 0xff0000ff }, // Red (ABGR format)
|
|
{ 0.5f, -0.5f, 0.0f, 0xff00ff00 }, // Green
|
|
{ 0.0f, 0.5f, 0.0f, 0xffff0000 }, // Blue
|
|
};
|
|
|
|
static const uint16_t s_triangleIndices[] = {
|
|
0, 1, 2,
|
|
};
|
|
|
|
// ============================================================================
|
|
// Main
|
|
// ============================================================================
|
|
|
|
int main(int argc, char* argv[]) {
|
|
std::cout << "========================================\n";
|
|
std::cout << "BgfxRenderer Visual Triangle Test\n";
|
|
std::cout << "========================================\n\n";
|
|
|
|
// Initialize SDL
|
|
if (SDL_Init(SDL_INIT_VIDEO) < 0) {
|
|
std::cerr << "SDL_Init failed: " << SDL_GetError() << "\n";
|
|
return 1;
|
|
}
|
|
|
|
// Create window
|
|
const int width = 800;
|
|
const int height = 600;
|
|
|
|
SDL_Window* window = SDL_CreateWindow(
|
|
"BgfxRenderer Triangle Test - Press ESC to exit",
|
|
SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED,
|
|
width, height,
|
|
SDL_WINDOW_SHOWN | SDL_WINDOW_RESIZABLE
|
|
);
|
|
|
|
if (!window) {
|
|
std::cerr << "SDL_CreateWindow failed: " << SDL_GetError() << "\n";
|
|
SDL_Quit();
|
|
return 1;
|
|
}
|
|
|
|
// Get native window handle
|
|
SDL_SysWMinfo wmi;
|
|
SDL_VERSION(&wmi.version);
|
|
if (!SDL_GetWindowWMInfo(window, &wmi)) {
|
|
std::cerr << "SDL_GetWindowWMInfo failed: " << SDL_GetError() << "\n";
|
|
SDL_DestroyWindow(window);
|
|
SDL_Quit();
|
|
return 1;
|
|
}
|
|
|
|
// Setup bgfx platform data
|
|
bgfx::PlatformData pd;
|
|
pd.ndt = wmi.info.x11.display;
|
|
pd.nwh = (void*)(uintptr_t)wmi.info.x11.window;
|
|
pd.context = nullptr;
|
|
pd.backBuffer = nullptr;
|
|
pd.backBufferDS = nullptr;
|
|
bgfx::setPlatformData(pd);
|
|
|
|
// Initialize bgfx
|
|
bgfx::Init init;
|
|
init.type = bgfx::RendererType::Count; // Auto-select
|
|
init.resolution.width = width;
|
|
init.resolution.height = height;
|
|
init.resolution.reset = BGFX_RESET_VSYNC;
|
|
init.platformData = pd;
|
|
|
|
std::cout << "Initializing bgfx...\n";
|
|
|
|
if (!bgfx::init(init)) {
|
|
std::cerr << "bgfx::init failed\n";
|
|
SDL_DestroyWindow(window);
|
|
SDL_Quit();
|
|
return 1;
|
|
}
|
|
|
|
// Print renderer info
|
|
const bgfx::Caps* caps = bgfx::getCaps();
|
|
std::cout << "Renderer: " << bgfx::getRendererName(caps->rendererType) << "\n";
|
|
std::cout << "Resolution: " << width << "x" << height << "\n";
|
|
|
|
// Setup view
|
|
bgfx::setViewClear(0, BGFX_CLEAR_COLOR | BGFX_CLEAR_DEPTH, 0x303030ff, 1.0f, 0);
|
|
bgfx::setViewRect(0, 0, 0, width, height);
|
|
|
|
// Initialize vertex layout
|
|
PosColorVertex::init();
|
|
|
|
// Create vertex buffer
|
|
bgfx::VertexBufferHandle vbh = bgfx::createVertexBuffer(
|
|
bgfx::makeRef(s_triangleVertices, sizeof(s_triangleVertices)),
|
|
PosColorVertex::layout
|
|
);
|
|
|
|
// Create index buffer
|
|
bgfx::IndexBufferHandle ibh = bgfx::createIndexBuffer(
|
|
bgfx::makeRef(s_triangleIndices, sizeof(s_triangleIndices))
|
|
);
|
|
|
|
// Load shaders
|
|
std::cout << "Loading shaders for " << bgfx::getRendererName(caps->rendererType) << "...\n";
|
|
|
|
bgfx::ShaderHandle vsh = loadShader(caps->rendererType, "vs_color");
|
|
bgfx::ShaderHandle fsh = loadShader(caps->rendererType, "fs_color");
|
|
|
|
if (!bgfx::isValid(vsh) || !bgfx::isValid(fsh)) {
|
|
std::cerr << "Failed to load shaders\n";
|
|
bgfx::shutdown();
|
|
SDL_DestroyWindow(window);
|
|
SDL_Quit();
|
|
return 1;
|
|
}
|
|
|
|
// Create program
|
|
bgfx::ProgramHandle program = bgfx::createProgram(vsh, fsh, true);
|
|
|
|
if (!bgfx::isValid(program)) {
|
|
std::cerr << "Failed to create shader program\n";
|
|
bgfx::shutdown();
|
|
SDL_DestroyWindow(window);
|
|
SDL_Quit();
|
|
return 1;
|
|
}
|
|
|
|
std::cout << "\n*** Rendering colored triangle ***\n";
|
|
std::cout << "Press ESC to exit or wait 5 seconds\n\n";
|
|
|
|
// Main loop
|
|
bool running = true;
|
|
uint32_t frameCount = 0;
|
|
Uint32 startTime = SDL_GetTicks();
|
|
const Uint32 testDuration = 5000; // 5 seconds
|
|
|
|
while (running) {
|
|
// Process events
|
|
SDL_Event event;
|
|
while (SDL_PollEvent(&event)) {
|
|
if (event.type == SDL_QUIT) {
|
|
running = false;
|
|
}
|
|
if (event.type == SDL_KEYDOWN && event.key.keysym.sym == SDLK_ESCAPE) {
|
|
running = false;
|
|
}
|
|
}
|
|
|
|
// Check timeout
|
|
if (SDL_GetTicks() - startTime > testDuration) {
|
|
running = false;
|
|
}
|
|
|
|
// Set view transform (identity for 2D)
|
|
float view[16];
|
|
float proj[16];
|
|
bx::mtxIdentity(view);
|
|
bx::mtxOrtho(proj, -1.0f, 1.0f, -1.0f, 1.0f, -1.0f, 1.0f, 0.0f, caps->homogeneousDepth);
|
|
bgfx::setViewTransform(0, view, proj);
|
|
|
|
// Touch view 0 to ensure clear
|
|
bgfx::touch(0);
|
|
|
|
// Set model transform (identity)
|
|
float model[16];
|
|
bx::mtxIdentity(model);
|
|
bgfx::setTransform(model);
|
|
|
|
// Set vertex and index buffers
|
|
bgfx::setVertexBuffer(0, vbh);
|
|
bgfx::setIndexBuffer(ibh);
|
|
|
|
// Set render state
|
|
uint64_t state = BGFX_STATE_WRITE_RGB
|
|
| BGFX_STATE_WRITE_A
|
|
| BGFX_STATE_WRITE_Z
|
|
| BGFX_STATE_DEPTH_TEST_LESS
|
|
| BGFX_STATE_MSAA;
|
|
|
|
bgfx::setState(state);
|
|
|
|
// Submit draw call
|
|
bgfx::submit(0, program);
|
|
|
|
// Submit frame
|
|
bgfx::frame();
|
|
frameCount++;
|
|
}
|
|
|
|
float elapsed = (SDL_GetTicks() - startTime) / 1000.0f;
|
|
float fps = frameCount / elapsed;
|
|
|
|
std::cout << "Test completed!\n";
|
|
std::cout << " Frames: " << frameCount << "\n";
|
|
std::cout << " Time: " << elapsed << "s\n";
|
|
std::cout << " FPS: " << fps << "\n";
|
|
|
|
// Cleanup
|
|
bgfx::destroy(program);
|
|
bgfx::destroy(vbh);
|
|
bgfx::destroy(ibh);
|
|
bgfx::shutdown();
|
|
SDL_DestroyWindow(window);
|
|
SDL_Quit();
|
|
|
|
std::cout << "\n========================================\n";
|
|
std::cout << "PASS: Colored triangle rendered!\n";
|
|
std::cout << "========================================\n";
|
|
|
|
return 0;
|
|
}
|