#include "../app/Gallery_Plot_Session.h" #include "../app/Gallery_Protocol.h" #include "../app/Web_Event_Adapter.h" #include "../app/Web_Plot_Session.h" #include "../app/common/Pixel_Frame.h" #include #include #include #include #include namespace renderive::web { namespace { std::uint32_t read_u32_le(const std::string& value, std::size_t offset) { return static_cast(value[offset]) | (static_cast(static_cast(value[offset + 1])) << 8U) | (static_cast(static_cast(value[offset + 2])) << 16U) | (static_cast(static_cast(value[offset + 3])) << 24U); } TEST(RenderiveWebBridge, DecodesTransportAndPointerEvents) { const auto resize = Web_Event_Adapter::decode( R"({"category":"event","type":"resize","width":640,"height":360})"); ASSERT_TRUE(resize); ASSERT_TRUE(std::holds_alternative(*resize)); EXPECT_EQ(std::get(*resize).size.width, 640); EXPECT_EQ(std::get(*resize).size.height, 360); const auto pointer = Web_Event_Adapter::decode( R"({"category":"event","type":"pointer_move","x":12.5,"y":7.25})"); ASSERT_TRUE(pointer); ASSERT_TRUE(std::holds_alternative(*pointer)); EXPECT_DOUBLE_EQ(std::get(*pointer).position.x, 12.5); EXPECT_FALSE(Web_Event_Adapter::decode(R"({"type":"frame"})")); } TEST(RenderiveWebBridge, EncodesRvp2PixelFrames) { const std::string frame = make_rgba8_pixel_frame(4, 3, 9); ASSERT_EQ(frame.size(), pixel_frame_header_size + 4U * 3U * 4U); EXPECT_EQ(frame.substr(0, 4), "RVP2"); EXPECT_EQ(read_u32_le(frame, 4), 4U); EXPECT_EQ(read_u32_le(frame, 8), 3U); EXPECT_EQ(read_u32_le(frame, 12), 16U); } TEST(RenderiveWebBridge, CurrentTwoDimensionalSceneRendersAfterResize) { Web_Plot_Session session; EXPECT_FALSE(session.handle(Viewport_Resize{{320, 200}})); const auto response = session.handle(Frame_Request{}); ASSERT_TRUE(response); ASSERT_EQ(response->type, Web_Response_Type::Pixels); ASSERT_GE(response->payload.size(), pixel_frame_header_size); EXPECT_EQ(response->payload.substr(0, 4), "RVP2"); EXPECT_EQ(read_u32_le(response->payload, 4), 320U); EXPECT_EQ(read_u32_le(response->payload, 8), 200U); } TEST(RenderiveWebGallery, CatalogUsesOneDefaultPageAndThreeDynamicStrategies) { const auto catalog = nlohmann::json::parse(Gallery_Protocol::catalog_json()); ASSERT_EQ(catalog.at("type"), "catalog"); EXPECT_EQ(catalog.at("cases").size(), 23U); EXPECT_EQ(catalog.at("frame_modes").size(), 3U); EXPECT_EQ(catalog.at("coverage").at("page_count"), 1U); EXPECT_EQ(catalog.at("coverage").at("canvas_count"), 23U); const auto request = Gallery_Protocol::open_request( R"({"category":"event","type":"gallery_open","case":"spectrum","frame_mode":"low_latency"})"); ASSERT_TRUE(request); EXPECT_EQ(request->case_id, "spectrum"); EXPECT_EQ(request->frame_mode, Gallery_Frame_Mode::Low_Latency); EXPECT_FALSE(Gallery_Protocol::open_request( R"({"category":"event","type":"gallery_open","case":"unknown","frame_mode":"low_latency"})")); } TEST(RenderiveWebGallery, TwoDimensionalGalleryPublishesStateAndPixels) { Gallery_Plot_Session session; const std::string open = R"({"category":"event","type":"gallery_open","case":"spectrum","frame_mode":"low_latency"})"; const auto opened = session.handle(Gallery_Request{Gallery_Request_Kind::Open, open}); ASSERT_TRUE(opened); ASSERT_EQ(opened->type, Web_Response_Type::Json); const auto state = nlohmann::json::parse(opened->payload); EXPECT_EQ(state.at("type"), "case_state"); EXPECT_EQ(state.at("case"), "spectrum"); const auto& controls = state.at("controls"); EXPECT_FALSE(controls.at("render_plan").at("nodes").empty()); EXPECT_TRUE(controls.at("performance_capture").at("controller").is_object()); EXPECT_TRUE(controls.at("performance_capture").at("sessions").is_array()); EXPECT_TRUE(controls.at("performance_capture").at("plans").is_array()); const auto& observer = state.at("telemetry").at("kernel_observer"); EXPECT_EQ(observer.at("state").at("viewport").at("width"), 560); EXPECT_EQ(observer.at("renderable_count"), 4U); EXPECT_EQ(state.at("actions").at("data").size(), 6U); const auto low_latency_pixels = session.handle(Frame_Request{}); ASSERT_TRUE(low_latency_pixels); EXPECT_EQ(low_latency_pixels->type, Web_Response_Type::Pixels); const auto switched = session.handle(Gallery_Request{ Gallery_Request_Kind::Action, R"({"category":"event","type":"gallery_action","action":"frame_strategy_manual"})"}); ASSERT_TRUE(switched); const auto switched_state = nlohmann::json::parse(switched->payload); EXPECT_EQ(switched_state.at("frame_mode"), "manual"); EXPECT_EQ(switched_state.at("telemetry").at("kernel_observer").at("mode"), "manual"); const auto rendered = session.handle(Frame_Request{}); ASSERT_TRUE(rendered); EXPECT_EQ(rendered->type, Web_Response_Type::Pixels); const std::string capture = R"({"category":"event","type":"gallery_action","action":"capture_next_frame"})"; const auto captured = session.handle( Gallery_Request{Gallery_Request_Kind::Action, capture}); ASSERT_TRUE(captured); const auto pixels = session.handle(Frame_Request{}); ASSERT_TRUE(pixels); ASSERT_EQ(pixels->type, Web_Response_Type::Pixels); EXPECT_EQ(pixels->payload.substr(0, 4), "RVP2"); const auto refreshed = session.handle(Gallery_Request{ Gallery_Request_Kind::Refresh, R"({"category":"event","type":"gallery_refresh"})"}); ASSERT_TRUE(refreshed); const auto capture_state = nlohmann::json::parse(refreshed->payload); const auto& sessions = capture_state.at("controls") .at("performance_capture").at("sessions"); ASSERT_EQ(sessions.size(), 1U); EXPECT_EQ(sessions.front().at("captured_count"), 1U); const auto first_pixel = pixels->payload.substr(pixel_frame_header_size, 4); bool contains_drawn_pixel = false; for (std::size_t offset = pixel_frame_header_size; offset + 4 <= pixels->payload.size(); offset += 4) { if (pixels->payload.compare(offset, 4, first_pixel) != 0) { contains_drawn_pixel = true; break; } } EXPECT_TRUE(contains_drawn_pixel); } } // namespace } // namespace renderive::web