|
| 1 | +#include <gtest/gtest.h> |
| 2 | + |
| 3 | +#include <Babylon/AppRuntime.h> |
| 4 | +#include <Babylon/Graphics/Device.h> |
| 5 | +#include <Babylon/Polyfills/Console.h> |
| 6 | +#include <Babylon/Polyfills/Window.h> |
| 7 | +#include <Babylon/Plugins/NativeEngine.h> |
| 8 | +#include <Babylon/ScriptLoader.h> |
| 9 | + |
| 10 | +#include <future> |
| 11 | +#include <iostream> |
| 12 | + |
| 13 | +extern Babylon::Graphics::Configuration g_deviceConfig; |
| 14 | + |
| 15 | +// Exercises all sub-vec4 uniform setter paths followed by a draw call to |
| 16 | +// verify that uniform data is padded to at least 16 bytes (vec4). |
| 17 | +// bgfx always reads 16 bytes from uniform storage, so anything smaller |
| 18 | +// causes a heap-buffer-overflow detectable by AddressSanitizer. |
| 19 | +// |
| 20 | +// Paths exercised: |
| 21 | +// setInt → NativeEngine::SetInt → Program::SetUniform (1 float + 3 pad) |
| 22 | +// setFloat → NativeEngine::SetFloat → SetFloatN<1> (1 float + 3 pad) |
| 23 | +// setVector2→ NativeEngine::SetFloat2 → SetFloatN<2> (2 floats + 2 pad) |
| 24 | +// setVector3→ NativeEngine::SetFloat3 → SetFloatN<3> (3 floats + 1 pad) |
| 25 | +TEST(UniformPadding, SubVec4UniformsDoNotOverflow) |
| 26 | +{ |
| 27 | + Babylon::Graphics::Device device{g_deviceConfig}; |
| 28 | + Babylon::Graphics::DeviceUpdate update{device.GetUpdate("update")}; |
| 29 | + |
| 30 | + device.StartRenderingCurrentFrame(); |
| 31 | + update.Start(); |
| 32 | + |
| 33 | + std::promise<void> done{}; |
| 34 | + |
| 35 | + Babylon::AppRuntime::Options options{}; |
| 36 | + options.UnhandledExceptionHandler = [&done](const Napi::Error& error) { |
| 37 | + std::cerr << "[Uncaught Error] " << Napi::GetErrorString(error) << std::endl; |
| 38 | + done.set_exception(std::make_exception_ptr(std::exception{})); |
| 39 | + }; |
| 40 | + |
| 41 | + Babylon::AppRuntime runtime{options}; |
| 42 | + runtime.Dispatch([&device](Napi::Env env) { |
| 43 | + device.AddToJavaScript(env); |
| 44 | + |
| 45 | + Babylon::Polyfills::Console::Initialize(env, [](const char* message, auto) { |
| 46 | + std::cout << message << std::endl; |
| 47 | + }); |
| 48 | + Babylon::Polyfills::Window::Initialize(env); |
| 49 | + Babylon::Plugins::NativeEngine::Initialize(env); |
| 50 | + }); |
| 51 | + |
| 52 | + Babylon::ScriptLoader loader{runtime}; |
| 53 | + loader.LoadScript("app:///Assets/babylon.max.js"); |
| 54 | + |
| 55 | + // Disable async shader compilation so shaders are ready immediately. |
| 56 | + // Create a scene with a ShaderMaterial that declares int, float, vec2, |
| 57 | + // and vec3 uniforms. Setting and rendering these exercises every sub-vec4 |
| 58 | + // code path through Program::SetUniform → DrawInternal → bgfx::setUniform. |
| 59 | + loader.Eval(R"( |
| 60 | + const engine = new BABYLON.NativeEngine(); |
| 61 | + engine.getCaps().parallelShaderCompile = null; |
| 62 | + const scene = new BABYLON.Scene(engine); |
| 63 | + scene.createDefaultCamera(); |
| 64 | + const sphere = BABYLON.MeshBuilder.CreateSphere("s", { diameter: 1 }, scene); |
| 65 | + const mat = new BABYLON.ShaderMaterial("test", scene, { |
| 66 | + vertexSource: ` |
| 67 | + attribute vec3 position; |
| 68 | + uniform mat4 worldViewProjection; |
| 69 | + void main() { gl_Position = worldViewProjection * vec4(position, 1.0); } |
| 70 | + `, |
| 71 | + fragmentSource: ` |
| 72 | + precision highp float; |
| 73 | + uniform int testInt; |
| 74 | + uniform float testFloat; |
| 75 | + uniform vec2 testVec2; |
| 76 | + uniform vec3 testVec3; |
| 77 | + void main() { |
| 78 | + gl_FragColor = vec4( |
| 79 | + float(testInt) + testFloat + testVec2.x + testVec3.x, |
| 80 | + testVec2.y + testVec3.y, |
| 81 | + testVec3.z, |
| 82 | + 1.0); |
| 83 | + } |
| 84 | + ` |
| 85 | + }, { |
| 86 | + attributes: ["position"], |
| 87 | + uniforms: ["worldViewProjection", "testInt", "testFloat", "testVec2", "testVec3"] |
| 88 | + }); |
| 89 | + mat.setInt("testInt", 1); |
| 90 | + mat.setFloat("testFloat", 2.0); |
| 91 | + mat.setVector2("testVec2", new BABYLON.Vector2(3.0, 4.0)); |
| 92 | + mat.setVector3("testVec3", new BABYLON.Vector3(5.0, 6.0, 7.0)); |
| 93 | + sphere.material = mat; |
| 94 | + scene.render(); |
| 95 | + if (!scene.isReady()) { throw new Error("Scene should be ready with synchronous shader compilation"); } |
| 96 | + )", "uniform_padding_test.js"); |
| 97 | + |
| 98 | + loader.Dispatch([&done](Napi::Env) { |
| 99 | + done.set_value(); |
| 100 | + }); |
| 101 | + |
| 102 | + done.get_future().get(); |
| 103 | + |
| 104 | + update.Finish(); |
| 105 | + device.FinishRenderingCurrentFrame(); |
| 106 | +} |
0 commit comments